[英]Validation of an std::initializer_list in constexpr context
[英]Weird behaviour constexpr with std::initializer_list
我試圖理解為什么編譯器在這里抱怨:
// 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;
}
編譯時產生的消息
g++ -std=c++11 -Wall -Werror cexpr_test.cpp
如下:
cexpr_test.cpp:在函數“int main()”中:cexpr_test.cpp:12:76:錯誤:“const std::initializer_list{((const char* const*)(&)), 1}”不是常量表達式 12 | constexpr std::initializer_list 損壞 {“但這不是?” }; |
令人困惑的是,為什么它在沒有任何問題的情況下構建第一個初始化列表。 我在這里缺少什么?
問題在於這里的broken
本身的初始化。 什么是std::initializer_list
以及它包含什么? 它是一個引用類型(即以某種方式引用另一個對象),並且它由一個 c 樣式數組支持。 這個 c 樣式數組的屬性決定了 initializer_list 是否可以是 constexpr 變量。 我們可以查閱[dcl.init.list]以獲得這些屬性。
5
std::initializer_list<E>
類型的對象是從初始化列表構造的,就好像實現生成並物化了一個“N
const E
數組”類型的純右值,其中N
是初始化列表中的元素數. 該數組的每個元素都使用初始值設定項列表的相應元素進行復制初始化,並且構造std::initializer_list<E>
對象以引用該數組。 [注意:為副本選擇的構造函數或轉換函數應可在初始化列表的上下文中訪問。 — 尾注 ] 如果需要縮小轉換來初始化任何元素,則程序格式錯誤。 [ 例子:struct X { X(std::initializer_list<double> v); }; X x{ 1,2,3 };
初始化的實現方式大致相當於:
const double __a[3] = {double{1}, double{2}, double{3}}; X x(std::initializer_list<double>(__a, __a+3));
假設實現可以用一對指針構造一個
initializer_list
對象。 — 結束示例 ]6數組與任何其他臨時對象具有相同的生命周期,除了從數組初始化一個
initializer_list
對象會延長數組的生命周期,就像將引用綁定到臨時對象一樣。 [ 例子:typedef std::complex<double> cmplx; std::vector<cmplx> v1 = { 1, 2, 3 }; void f() { std::vector<cmplx> v2{ 1, 2, 3 }; std::initializer_list<int> i3 = { 1, 2, 3 }; } struct A { std::initializer_list<int> i4; A() : i4{ 1, 2, 3 } {} // ill-formed, would create a dangling reference };
對於
v1
和v2
,initializer_list
對象是函數調用中的參數,因此為{ 1, 2, 3 }
創建的數組具有完整的表達式生命周期。 對於i3
,initializer_list
對象是一個變量,因此數組在變量的生命周期內持續存在。 對於i4
,initializer_list
對象在構造函數的 ctor-initializer 中初始化,就像通過將臨時數組綁定到引用成員一樣,因此程序格式錯誤([class.base.init])。 — 結束示例 ] [ 注意:如果可以分配具有相同初始值設定項的顯式數組,則實現可以在只讀內存中自由分配數組。 — 尾注 ]
所以這個數組就像任何其他由常量引用引用的臨時對象一樣。 這意味着我們實際上可以將您的最小示例減少到更小
constexpr int test_cexpr(int const & x)
{
return x;
}
int main()
{
constexpr int r1 = test_cexpr(0);
constexpr int const &broken = 0;
constexpr int r2 = test_cexpr(broken);
return r1 + r2;
}
這會產生完全相同的行為和錯誤。 我們可以直接將0
作為參數傳遞給 constexpr 函數,引用綁定,我們甚至可以在函數內部引用它。 然而,constexpr 引用不能用 0 初始化。零不是一個有效的初始化器的原因是它需要物化一個臨時的int
對象。 此臨時變量不是靜態變量,因此不能用於初始化 constexpr 引用。 就那么簡單。
同樣的推理適用於您的情況。 物化的臨時數組不是具有靜態存儲持續時間的對象,因此它不能用於初始化 constexpr 引用類型。
它在直接將參數傳遞給test_cexpr
時起作用的原因是相應的參數本身不是 constexpr 變量。 這意味着它可以成功綁定。 之后,它所綁定的東西必須在常量表達式中可用。 無需對此進行過多詳細說明:由於在這種情況下臨時變量具有完整的表達式生命周期(而不是生命周期延長),因此它可用於常量表達式。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.