std::initializer_list什么时候可以是constexpr?

8
根据cppreference.com的说法,std::initializer_lists具有constexpr constructorsconstexpr size methods(自C++14起)。
尽管我使用的编译器似乎同意constexpr初始化列表的大小确实是constexpr的,在某些情况下它却不认为我的列表是constexpr的。由于std::initializer_lists可能涉及一些“编译器魔法”,我开始怀疑constexpr是否适用于它们,与非魔法对象的方式不完全相同。
我进入了Compiler Explorer并发现主要编译器在这个问题上意见不一致
那么以下四种情况的正确行为(根据标准)是什么呢?
#include <initializer_list>

using size_type = std::initializer_list<int>::size_type;

template <typename T>
size_type Foo(std::initializer_list<T> const &list) {
    return list.size();
}

int main() {
    // 1.  Example based on
    // https://en.cppreference.com/w/cpp/utility/initializer_list/size
    // gcc: works
    // clang: no viable c'tor or deduction guide
    // msvc: works
    static_assert(std::initializer_list{1, 2, 3}.size() == 3);

    // 2.  Make a constexpr std::initializer_list<T> with T deduced
    // gcc: not constant expression
    // clang: no viable c'tor or deduction guide
    // msvc: works
    constexpr auto the_list = std::initializer_list{1, 2, 3};

    // 3.  Static assert using constexpr size
    // gcc: fails because of above
    // clang: fails because of above
    // msvc: works
    static_assert(the_list.size() == 3);

    // 4.  Extract the size via a constexpr function
    // gcc: fails because of above
    // clang: fails because of above
    // msvc: expression did not evaluate to a constant
    constexpr auto the_list_size = Foo(the_list);
    return 0;
}
  • "gcc" 指的是带有 -std=c++20(也经过 13.1 版本测试)的 x86-64 gcc(主干版本)
  • "clang" 指的是带有 -std=c++20(也经过 16.0.0 版本测试)的 x86-64 clang(主干版本)
  • "msvc" 指的是带有 /std:c++20 的 x64 msvc v19.latest

我本来期望这四种情况都能编译通过。但是一些编译器拒绝了其中一些情况,让我重新考虑我的理解。编译器是否拒绝了正确的代码(或接受了错误的代码)?我是否无意中依赖于实现定义的行为?标准是否存在歧义?


我能找到的最相似的问题是给std::array赋值initializer_list,但那里面的细节是针对std::array的,而不适用于我的示例。

3
Foo 没有标记为 constexpr... - Jarod42
3
Foo 没有标记为 constexpr... - Jarod42
1
Clang关于initializer_list没有可行的推导指南的错误是一个bug,该bug在issues/60876中进行了跟踪。 - 康桓瑋
1
Clang关于initializer_list没有可行的推导指南的错误是一个bug,已在issues/60876进行追踪。 - 康桓瑋
1
Clang关于initializer_list没有可行的推导指南的错误是一个bug,该bug已在issues/60876中进行跟踪。 - undefined
显示剩余11条评论
1个回答

5

std::initializer_list{1, 2, 3} 是一个clang的bug(它无法正确推断出<int>)。将其替换为std::initializer_list<int>{1, 2, 3}会导致与gcc相同的行为。或者您可以使用auto the_list = { 1, 2, 3 };来绕过这个问题。

constexpr auto the_list = std::initializer_list<int>{1, 2, 3};的问题与constexpr const int& the_number = 123;相同。就像const int&一样,std::initializer_list绑定到一个临时数组。而且,就像const int&一样,该临时数组的生命周期延长到initializer_list变量的生命周期。

如果一个`constexpr`变量绑定到一个临时对象,那么这个临时对象必须具有静态存储期。因此,`static constexpr auto the_list = std::initializer_list{1, 2, 3};`是有效的(同样适用于`static constexpr const int& the_number = 123;`)。

[dcl.init.list]p5

从初始化列表构造一个类型为`std::initializer_list`的对象,就好像实现生成并实现化了一个类型为“长度为N的const E数组”的prvalue([conv.rval]),然后构造了一个`std::initializer_list`对象来引用该数组。[...]

[dcl.init.list]p6

数组的寿命与任何其他临时对象相同([class.temporary]),但是从数组初始化initializer_list对象会像将引用绑定到临时对象一样延长数组的寿命。 (例如,auto the_list = {1, 2, 3};,const int [3]具有自动存储期限,但是static auto the_list = {1, 2, 3};,const int [3]具有静态存储期限)

[expr.const]p11:

如果一个实体是一个常量表达式的允许结果,那么它是一个具有静态存储期的对象,该对象要么不是临时对象,要么是一个值满足上述约束的临时对象。

而你最后的问题是 Foo 不是一个 constexpr 函数。修复后的版本:

#include <initializer_list>

using size_type = std::initializer_list<int>::size_type;

template <typename T>
constexpr size_type Foo(std::initializer_list<T> const &list) {
    return list.size();
}

int main() {
    static_assert(std::initializer_list<int>{1, 2, 3}.size() == 3);

    static constexpr auto the_list = {1, 2, 3};

    static_assert(the_list.size() == 3);

    constexpr auto the_list_size = Foo(the_list);
    return 0;
}

在这三个编译器上都可以编译。以前的MSVC对临时数组的生命周期过于宽松。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接