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

Adr*_*thy 8 c++ language-lawyer constexpr stdinitializerlist c++20

根据 cppreference.com,std::initializer_lists 具有constexpr 构造函数constexpr 大小方法(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;
}
Run Code Online (Sandbox Code Playgroud)
  • “gcc”表示 x86-64 gcc(主干)-std=c++20(也使用 13.1 进行测试)
  • “clang”表示 x86-64 clang(主干)-std=c++20(也使用 16.0.0 进行测试)
  • “msvc”表示 x64 msvc v19.latest/std:c++20

我本来希望所有四个案例都能编译。但有些编译器拒绝了其中的一些,导致我重新考虑我的理解。编译器是否拒绝正确的代码(或接受错误的代码)?我是否无意中依赖了实现定义的行为?标准是否含糊?


我能找到的最接近的现有问题是分配一个initializer_list to std::array,但其中的细节是特定于std::array 的,这在我的示例中不是这种情况。

Art*_*yer 5

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

\n

的问题与constexpr auto the_list = std::initializer_list<int>{1, 2, 3};的问题相同constexpr const int& the_number = 123;。与 with 类似const int&std::initializer_list绑定到临时对象(在本例中为数组)。同样const int&,临时变量的生命周期也延长到initializer_list变量的生命周期。

\n

如果constexpr变量绑定到临时变量,则该临时变量需要具有静态存储持续时间。所以,static constexpr auto the_list = std::initializer_list<int>{1, 2, 3};有效(也是如此static constexpr const int& the_number = 123;)。

\n

[dcl.init.list]p5

\n
\n

类型的对象std\xe2\x80\x8b::\xe2\x80\x8binitializer_\xc2\xadlist<E>是从初始值设定项列表构造的,就好像实现生成并具体化 ([conv.rval]) N const E \xe2\x80\ x9d、[...] 的 \xe2\x80\x9carray 类型的纯右值,并且该std\xe2\x80\x8b::\xe2\x80\x8binitializer_\xc2\xadlist<E>对象被构造为引用该数组。[...]

\n
\n

[dcl.init.list]p6

\n
\n

该数组与任何其他临时对象 ([class.temporary]) 具有相同的生命周期,只不过initializer_\xc2\xadlist从数组初始化对象会延长数组的生命周期,就像将引用绑定到临时对象一样。

\n
\n

(例如auto the_list = { 1, 2, 3 };const int[3]具有自动存储持续时间,但是static auto the_list = { 1, 2, 3 };const int[3]具有静态存储持续时间)

\n

[expr.const]p11 :

\n
\n

如果实体是具有静态存储持续时间对象,并且不是临时对象或者是其值满足上述约束的临时对象,则实体是常量表达式的允许结果,[...]

\n
\n
\n

你的最后一个问题不是Foo函数constexpr。固定版本:

\n
#include <initializer_list>\n\nusing size_type = std::initializer_list<int>::size_type;\n\ntemplate <typename T>\nconstexpr size_type Foo(std::initializer_list<T> const &list) {\n    return list.size();\n}\n\nint main() {\n    static_assert(std::initializer_list<int>{1, 2, 3}.size() == 3);\n\n    static constexpr auto the_list = {1, 2, 3};\n\n    static_assert(the_list.size() == 3);\n\n    constexpr auto the_list_size = Foo(the_list);\n    return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在三个给定的编译器上编译。之前MSVC对于临时数组的生命周期太宽松了。

\n