关于常量表达式的困惑

Mor*_*enn 15 c++ compile-time constant-expression constexpr c++11

这是本主题的某种后续行动,涉及其中的一小部分.与前一个主题一样,让我们​​考虑一下我们的编译器有和的constexpr函数.现在,让我们直截了当地说.std::initializer_liststd::array

这有效:

#include <array>
#include <initializer_list>

int main()
{
    constexpr std::array<int, 3> a = {{ 1, 2, 3 }};
    constexpr int a0 = a[0];
    constexpr int a1 = a[1];
    constexpr int a2 = a[2];
    constexpr std::initializer_list<int> b = { a0, a1, a2 };

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这不是:

#include <array>
#include <initializer_list>

int main()
{
    constexpr std::array<int, 3> a = {{ 1, 2, 3 }};
    constexpr std::initializer_list<int> b = { a[0], a[1], a[2] };

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

它崩溃了这个错误:

error: 'const std::initializer_list<int>{((const int*)(&<anonymous>)), 3u}' is not a constant expression
Run Code Online (Sandbox Code Playgroud)

虽然我同时阅读了一些关于constexpr和持续表达的论文,但这种行为对我来说仍然没有任何意义.为什么第一个例子被认为是有效的常量表达而不是第二个?我欢迎任何解释,以便我以后可以安息.

注意:我会马上准确,Clang将无法编译第一个片段,因为它没有实现constexpr为C++ 14计划的库添加.我使用GCC 4.7.

编辑:好的,这里有一个很好的例子来展示什么被拒绝和什么不是:

#include <array>
#include <initializer_list>

constexpr int foo = 42;
constexpr int bar() { return foo; }
struct eggs { int a, b; };

int main()
{
    constexpr std::array<int, 3> a = {{ 1, 2, 3 }};
    constexpr int a0 = a[0];
    constexpr int a1 = a[1];
    constexpr int a2 = a[2];

    // From Xeo and Andy tests
    constexpr std::array<int, 1> a = { bar() }; // OK
    constexpr std::array<int, 3> b = {{ a[0], a[1], a[2] }}; // OK
    std::initializer_list<int> b = { a[0], a[1], a[2] }; // OK
    constexpr std::initializer_list<int> b = { a0, a1, a2 }; // OK
    constexpr std::initializer_list<int> b = { foo }; // OK
    constexpr std::initializer_list<int> c = { bar() }; // ERROR
    constexpr std::initializer_list<int> b = { a[0], a[1], a[2] }; // ERROR

    // From Matheus Izvekov and Daniel Krügler
    constexpr eggs good = { 1, 2 }; // OK
    constexpr std::initializer_list<eggs> bad = { { 1, 2 }, { 3, 4 } }; // ERROR
    constexpr std::initializer_list<eggs> bad2 = { good, good }; // ERROR

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Wlo*_* K. 2

我弄清楚这里发生了什么:

\n\n
 constexpr std::initializer_list<int> b = { a[0], a[1], a[2] };\n
Run Code Online (Sandbox Code Playgroud)\n\n

a[0]类型const int&隐式转换为类型的临时值const int。\n然后 g++ 将其转换为const int*传递给initializer_list私有构造函数。\n在最后一步中,它获取临时值的地址,因此它不是常量表达式。

\n\n

问题在于隐式转换为 const int。例子:

\n\n
constexpr int v = 1;\nconst int& r = v; // ok\nconstexpr int& r1 = v; // error: invalid initialization of reference of\n                       // type \xe2\x80\x98int&\xe2\x80\x99 from expression of type \xe2\x80\x98const int\xe2\x80\x99\n
Run Code Online (Sandbox Code Playgroud)\n\n

clang 中也有同样的行为。

\n\n

我认为这种转换是合法的,没有任何相反的说法。

\n\n

关于const int&转换const int,[expr] 第 5 段:

\n\n
\n

如果表达式最初的类型为 \xe2\x80\x9c 到 T\xe2\x80\x9d 的引用,则在进行任何进一步分析之前,该类型将调整为 T。该表达式指定引用所表示的对象或函数,并且该表达式是左值或x值,具体取决于表达式。

\n
\n\n

在这种情况下,表达式的结果a[0]是类型的临时 xvalue 。const int

\n\n

关于 constexpr 初始值设定项中的隐式转换,[dcl.constexpr] 第 9 段:

\n\n
\n

...用于转换初始化表达式的每个隐式转换和用于初始化的每个构造函数调用都应是常量表达式中允许的转换之一。

\n
\n\n

关于获取临时地址,[expr.const] 第 2 段:

\n\n
\n

...使用参数调用 constexpr 函数,当被函数调用替换替换时,不会生成常量表达式;[ 例子:

\n\n
constexpr const int* addr(const int& ir) { return &ir; } // OK\nstatic const int x = 5;\nconstexpr const int* xp = addr(x); // OK: (const int*)&(const int&)x is an\n                                   // address contant expression\nconstexpr const int* tp = addr(5); // error, initializer for constexpr variable\n                                   // not a constant expression;\n                                   // (const int*)&(const int&)5 is not a\n                                   // constant expression because it takes\n                                   // the address of a temporary\n
Run Code Online (Sandbox Code Playgroud)\n\n

\xe2\x80\x94 结束示例]

\n
\n