Ale*_*c C 6 c++ string-literals initializer-list language-lawyer constexpr
我试图理解为什么编译器在这里抱怨:
// cexpr_test.cpp
#include <initializer_list>
constexpr int test_cexpr(std::initializer_list<const char*> x)
{
return (int) (*x.begin())[0]; // ensuring the value isn't optimized out.
}
int main()
{
constexpr int r1 = test_cexpr({ "why does this work," });
constexpr std::initializer_list<const char*> broken { "but this doesn't?" };
constexpr int r2 = test_cexpr(broken);
return r1 + r2;
}
Run Code Online (Sandbox Code Playgroud)
编译时产生的消息
g++ -std=c++11 -Wall -Werror cexpr_test.cpp
Run Code Online (Sandbox Code Playgroud)
如下:
cexpr_test.cpp:在函数“int main()”中:cexpr_test.cpp:12:76:错误:“const std::initializer_list{((const char* const*)(&)), 1}”不是常量表达式 12 | constexpr std::initializer_list 损坏 {“但这不是?” }; |
令人困惑的是,为什么它在没有任何问题的情况下构建第一个初始化列表。我在这里缺少什么?
问题出broken
在它本身的初始化上。a 是什么std::initializer_list
?它包含什么?它是一个引用类型(即以某种方式引用另一个对象),并且由 C 样式数组支持。此 C 风格数组的属性决定了initializer_list 是否可以是 constexpr 变量。我们可以查阅[dcl.init.list]来了解这些属性。
\n\n\n5从初始值设定项列表构造\n类型的对象
\n\nstd\xe2\x80\x8b::\xe2\x80\x8binitializer_\xc2\xadlist<E>
,就好像实现生成并具体化\xe2\x80\x9d 的N
const E
\xe2\x80\x9carray 类型的纯右值,其中N
\n 是\n 中的元素数量初始化列表。该数组的每个元素都使用初始值设定项列表的相应元素进行复制初始化,并且std\xe2\x80\x8b::\xe2\x80\x8binitializer_\xc2\xadlist<E>
对象被构造为引用该数组。[\xe2\x80\x89注意:为副本选择的构造函数或转换函数应可在初始化列表的上下文中访问。\xe2\x80\x89\xe2\x80\x94\xe2\x80\x89end note\xe2\x80\x89] 如果需要缩小\n 转换来初始化任何元素,则程序\n 格式错误。[\xe2\x80\x89示例:Run Code Online (Sandbox Code Playgroud)\n\nstruct X {\n X(std::initializer_list<double> v);\n};\nX x{ 1,2,3 };\n
初始化的实现方式大致相当于:
\n\nRun Code Online (Sandbox Code Playgroud)\n\nconst double __a[3] = {double{1}, double{2}, double{3}};\nX x(std::initializer_list<double>(__a, __a+3));\n
假设实现可以
\n\ninitializer_\xc2\xadlist
用一对指针构造一个 \n 对象。\xe2\x80\x89\xe2\x80\x94\xe2\x80\x89结束示例\xe2\x80\x89]6该数组与任何其他临时对象具有相同的生命周期,\n 除了
\n\ninitializer_\xc2\xadlist
从数组初始化对象\n 延长了数组的生命周期之外,\n 与将引用绑定到临时对象\n 完全一样。[\xe2\x80\x89示例:Run Code Online (Sandbox Code Playgroud)\n\ntypedef std::complex<double> cmplx;\nstd::vector<cmplx> v1 = { 1, 2, 3 };\n\nvoid f() {\n std::vector<cmplx> v2{ 1, 2, 3 };\n std::initializer_list<int> i3 = { 1, 2, 3 };\n}\n\nstruct A {\n std::initializer_list<int> i4;\n A() : i4{ 1, 2, 3 } {} // ill-formed, would create a dangling reference\n};\n
对于
\nv1
和v2
,该initializer_\xc2\xadlist
对象是函数调用中的参数,因此为其创建的数组{ 1, 2, 3 }
具有完整表达式生命周期。对于i3
,该initializer_\xc2\xadlist
对象是\n 变量,因此该数组在该变量的生命周期内持续存在。\n 对于i4
,该initializer_\xc2\xadlist
对象在构造函数的构造函数初始化程序中初始化\n,就像将临时数组绑定到\n引用成员,因此程序格式错误([class.base.init])。\n \xe2\x80\x89\xe2\x80\x94\xe2\x80\x89end example\xe2\x80\x89] [\ xe2\x80\x89注意:如果可以分配具有相同初始值设定项的显式数组,则实现可以自由地在只读内存中分配数组。\xe2\x80\x89\xe2\x80\x94\xe2\x80\x89尾注\xe2\x80\x89]
所以这个数组就像任何其他由常量引用引用的临时对象一样。这意味着我们实际上可以将您的最小示例简化为更小的示例
\n\nconstexpr int test_cexpr(int const & x)\n{\n return x; \n}\n\nint main()\n{\n constexpr int r1 = test_cexpr(0);\n\n constexpr int const &broken = 0;\n constexpr int r2 = test_cexpr(broken);\n\n return r1 + r2;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n这会产生完全相同的行为和错误。我们可以0
直接作为参数传递给 constexpr 函数,引用绑定,甚至可以在函数内部引用它。但是,constexpr 引用不能用 0 进行初始化。零不是有效初始化器的原因是它需要具体化临时对象int
。该临时变量不是静态变量,因此不能用于初始化 constexpr 引用。就那么简单。
同样的推理也适用于你的情况。物化的临时数组不是具有静态存储持续时间的对象,因此它不能用于初始化 constexpr 引用类型。
\n\n直接将参数传递给时它起作用的原因test_cexpr
是相应的参数本身不是 constexpr 变量。这意味着它可以绑定成功。之后,它所绑定的东西必须可以在常量表达式中使用。无需对此进行太多详细说明:由于这种情况下的临时变量具有完整表达式的生存期(而不是扩展的生存期),因此它可以在常量表达式中使用。
归档时间: |
|
查看次数: |
426 次 |
最近记录: |