下面如果我使用 constexpr,编译器显然会说“表达式必须有一个常量值”,这在 MSVC 和 GCC 上会发生:

int main() {
    constexpr auto nnn = {
        "this", "sentence", "is", "not", "a", "sentence",
            "this", "sentence", "is", "a", "hoax"
    };
}

如果没有 ,constexpr编译器就可以推断出该nnn变量是std::initializer_list<const char*>。并且std::initialiser_list该类有一个 constexpr 构造函数,为什么不能这样呢constexpr

13

  • 2
    这个问题类似于:。如果您认为它们不同,请编辑问题,说明它们有何不同和/或该问题的答案对您的问题无济于事。


    – 

  • 可能是为了完成重复,括号初始化导致推断出std::initializer_list不能在常量表达式中使用的。抱歉,我现在没有标准中的精确陈述(但编译器对此非常明确:))。我认为可能是缺少 constexpr 复制/移动构造函数(nn推断为std::initializer_list并从右侧构造复制/移动)


    – 

  • Clang 错误消息更具参考性:“注意:指向临时子对象的指针不是常量表达式”


    – 

  • 顺便说一句,我删除了 dupe 标志,因为它没有解决 constexpr 部分:


    – 

  • 2
    中使用时则不行


    – 


最佳答案
2

类型推导没有失败。编译器推导auto为。如果你写而不是 ,std::initializer_list<const char*>你会得到相同的结果std::initializer_list<const char*>auto

初始化不能是常量表达式(只有在推导完所有类型后才能检查)。

它不能是一个常量表达式,因为它的std::initializer_list工作方式就好像在同一范围内定义一个具有相同存储持续时间的数组来保存元素,并且就好像对象std::initializer_list保存指向该数组的开头和结尾的指针一样。

因为在您的示例中该数组具有自动存储持续时间,所以std::initialization_list对象的初始化不能是常量表达式,因为常量表达式的结果只能包含指向具有静态存储持续时间的对象的指针。 (每次执行初始化时,指向具有自动存储持续时间的对象的指针的值都会发生变化,因此不能是常量表达式的结果。)

std::initializer_list您可以使用强制未命名数组具有静态存储持续时间(以及) static

int main() {
    constexpr static auto nnn = {
        "this", "sentence", "is", "not", "a", "sentence",
            "this", "sentence", "is", "a", "hoax"
    };
}

7

  • 您对“好像”部分有标准引述吗?


    – 

  • 1
    @WeijunZhou 。它与我的回答中描述的并不完全相同,因为我想避免谈论临时对象,但效果是一样的。


    – 

  • 1
    @WeijunZhou 并且可以说“并且std​::​initializer_list<E> 对象被构造为引用该数组”应该以某种方式在 [expr.const] 中涵盖,但目前并非如此。


    – 

  • initialiser_list 要做的就是包含一个 const char 指针数组,每个指针都指向在程序生命周期内有效的静态内存。initialiser_list 本身将具有自动存储持续时间,这是真的,但基本上任何具有 constexpr 构造函数的类都是这种情况,并且可以将它们分配给 constexpr 而无需将其设为静态。这没有意义。


    – 

  • @Zebrafish 可以,但没有指定。指定数组具有与initializer_list对象相同的生命周期。例如,(假设您被允许)如果您递归main调用,标准保证nnn.data()每次递归调用都会有不同的指针值。您的建议在非 情况下通常也不起作用constexpr,因为初始化器应该能够依赖于运行时值,而运行时值在每次调用中可能都不同。


    – 

OP 提到的构造函数constexpr是创建空列表的默认构造函数。实际可用的构造函数没有任何标准接口。常量表达式的一个主要要求是每个调用的函数都是可见的、内联的和指定的constexpr。结果,std::initializer_list就是不能constexpr。当 C++11 即将完成时,它看起来像是一种必要的憎恶。我希望它现在已经被弃用了。我无法想象没有替代方案的任何用例。作为函数/构造函数参数的用法可以用std::span<const T>它处理,只需要多输入一点:

constexpr auto foo(std::span<std::uint64_t> arr){
   return std::ranges::fold_left(arr,0ull,std::plus<>{});
};

auto constexpr N = foo(std::array{1ull, 2, 3});

其他用法只是重复std::array

constexpr std::array nnn = {
        "this"sv, "sentence", "is", "not", "a", "sentence",
            "this", "sentence", "is", "a", "hoax"
    };//fixed-size array of std::string_views

从 C++23 开始,所有 std 容器都有范围构造函数,因此可以传入std::array

std::vector<int> vec(std::from_range, std::array{1,2,3,4});

在我看来,std::initializer_list没有更多实际用例。一切都已经有更好的替代品了。

3

  • 1
    std::initializer_list根本就不可能constexprstatic编译器通过添加本地范围或仅在全局范围来允许它……


    – 

  • @Jarod42 如果用 constexpr 标记,为什么它不能是 constexpr?这太令人困惑了。我知道 constexpr 不能保证 constexpr,但 consteval 可以,但是将关键字 constexpr 附加到“不能”是 constexpr 的类型确实令人困惑。


    – 

  • @Zebrafish:不确定为什么我被提及 (@)。我的评论是为了发现答案中的问题。


    –