Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confusion about constant expressions

This is some kind of follow-up for this topic and deals about a little part of it. As with the previous topic, let's consider that our compiler has constexpr functions for std::initializer_list and std::array. Now, let's go straight to the point.

This works:

#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;
}

This does not:

#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;
}

It crashes with this error:

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

Even though I read some papers about constexpr and constant expressions meanwhile, this behaviour still does not make any sense for me. How come the first example is considered a valid constant expression and not the second one? I would welcome any explanation so that I can rest in peace afterwards.

NOTE: I will precise it right away, Clang will not be able to compile the first snippet since it does not implement the constexpr library additions that are planned for C++14. I used GCC 4.7.

EDIT: Ok, here comes the big example to show what is rejected and what is not:

#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;
}
like image 818
Morwenn Avatar asked Apr 17 '13 10:04

Morwenn


People also ask

Why is the initialization of B not a constant expression?

Since b has automatic storage duration, when the std::initializer_list<int> object binds to the const int [3] temporary, the temporary is also given automatic storage duration, so the initialization of b is not a constant expression because it refers to the address of an object that does not have static storage duration.

What is the result of [0] expression?

The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression. The result of a [0] expression is the temporary xvalue of type const int in that case. ...

Which expression evaluates to the address of an object?

[...] An address constant expression [...] evaluates to the address of an object with static storage duration.

What are some idiomatic expressions to say things proceeding slowly?

This idiomatic expression can be used to say that you are in a less than ideal situation. 55. Inching forward - Making slow progress When you say this, you’re saying things are proceeding slowly. 56. Keep on the straight and narrow – Keep out of trouble


1 Answers

I figured out what is going on here:

 constexpr std::initializer_list<int> b = { a[0], a[1], a[2] };

a[0] of type const int& implicitly converts to a temporary of type const int. Then g++ converts it to const int* to pass into the initializer_list private constructor. In the last step it takes address of a temporary, so it is not a constant expression.

The problem is in implicit conversion to const int. Example:

constexpr int v = 1;
const int& r = v; // ok
constexpr int& r1 = v; // error: invalid initialization of reference of
                       // type ‘int&’ from expression of type ‘const int’

The same behavior is in clang.

I think this conversion is legal, nothing says the opposite.

About const int& to const int conversion, [expr] paragraph 5:

If an expression initially has the type “reference to T” , the type is adjusted to T prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression.

The result of a[0] expression is the temporary xvalue of type const int in that case.

About implicit conversions in constexpr initializer, [dcl.constexpr] paragraph 9:

... Each implicit conversion used in converting the initializer expressions and each constructor call used for the initialization shall be one of those allowed in a constant expression.

About taking address of temporary, [expr.const] paragraph 2:

...an invocation of a constexpr function with arguments that, when substituted by function invocation substitution, do not produce a constant expression; [ Example:

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

— end example ]

like image 60
Wlodzislav K. Avatar answered Sep 20 '22 02:09

Wlodzislav K.