2

Say I want to build a graph at compile time, using some algorithm, and then count how many nodes ended up in the graph. This seems like an ideal situation for constexpr, rather than template metaprogramming, since the goal is a computation that yields a value, rather than really being about types. I have some code that works, but the feature is so new I'm afraid the compilers are being lenient, and I could interpret part of the standard as saying I can't do this.

#include <iostream>

struct A { int x; constexpr A(int i) noexcept : x{i} {} };
struct B { A& a; constexpr B(A& a) noexcept : a{a} {} };

constexpr int foo() { 
    A a{55};
    B b{a};
    return b.a.x;
}

template<int N>
void output()
{
    std::cout << N << std::endl;
}

int main() {
    // to be absolutely sure compile time eval'd,
    // pass as template arg
    constexpr auto b = foo();
    output<b>();
}

Both the a and b instances are created at compile time, and they have the same lifetime so this should be "safe". But a is a non-static object, and this part of the standard seems to say that's not allowed:

An entity is a permitted result of a constant expression if it is an object with static storage duration that is either not a temporary object or is a temporary object whose value satisfies the above constraints, or it is a function.

So can I or can't I? GCC and clang are both fine with it.

Joseph Garvin
  • 20,727
  • 18
  • 94
  • 165
  • The code would be unproblematic in general because the result isn't used at compile time. A `constexpr` function does not *have* to produce a compile time constant. It *can* do that when supplied with compile time constant arguments (if any). Not sure about your case though. But the presented code doesn't exercise the compile time constant-ness. – Cheers and hth. - Alf Jul 30 '15 at 22:23
  • @Jarod42: oversight, now it's changed, and same result. – Joseph Garvin Jul 30 '15 at 22:27
  • @Cheersandhth.-Alf: I've added constexpr to the main declaration of 'b' to force it to be compile time. – Joseph Garvin Jul 30 '15 at 22:27
  • Whereas `A a{55};` may be `constexpr`, `B b{a};` cannot be (with clang error message `note: reference to 'a' is not a constant expression`) [Demo](http://coliru.stacked-crooked.com/a/168c124abd16c7fc) – Jarod42 Jul 30 '15 at 23:05
  • Also see [how to initialize a constexpr reference](http://stackoverflow.com/q/28614591/1708801) – Shafik Yaghmour Jul 30 '15 at 23:58
  • @Jarod42: how do you get the error? per my link clang is fine with it, no warnings or errors. – Joseph Garvin Jul 31 '15 at 14:19
  • In @Jarod42 's demo `A` and `B` are declared constexpr inside `foo()`, while in the post they are not. – Jay Miller Jul 31 '15 at 16:04
  • @Jarod42 oops didn't realize you had a demo linked, thought it was part of your username – Joseph Garvin Jul 31 '15 at 17:16

3 Answers3

5

Yes, your example is conforming.

The special thing about C++14 relaxed constexpr is that intermediate results inside the evaluation of a constant expression do not themselves need to be constant expressions.

return applies an lvalue-to-rvalue conversion to b.a.x, because the function returns by value, and b.a.x is:

a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e

(N4527 §5.20/2.7.4)

If you tried to save a (dangling) reference to b.a.x, that would be a problem. That would not be a "permitted result of a constant expression" per your quote.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
0

In your example:

B b{a};

b is not a constexpr variable the draft C++14 section 7.1.5 [dcl.constexpr]p5 which says:

A constexpr specifier used in an object declaration declares the object as const. Such an object shall have literal type and shall be initialized. If it is initialized by a constructor call, that call shall be a constant expression (5.19). Otherwise, or if a constexpr specifier is used in a reference declaration, every fullexpression that appears in its initializer shall be a constant expression. [ Note: Each implicit conversion used in converting the initializer expressions and each constructor call used for the initialization is part of such a full-expression. —end note ]

does not apply but if we modify your example slightly:

int main() {
    constexpr auto b = foo();
    A a1(42) ;
    constexpr B b1( a1 ) ;
}

and introduce b1 which is a constexpr variable then this will not work (see it live), clang says:

error: 'B{a1}' is not a constant expression
     constexpr B b1( a1 ) ;
                        ^

if we further modify the example from above as follows though:

static A a1(42) ;
constexpr B b1( a1 ) ;

it will work. A constexpr function is available for use in a constexpr but if it does not satisfy the requirements it will not yield a constant expression.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • A `static` variable can't be initialized in a constant expression. It would be a side effect. See §7.1.5/3.4.4 – Potatoswatter Jul 31 '15 at 07:25
  • @Potatoswatter I did not say it could, my live example does not do that, I added more context to my code to clarify this. – Shafik Yaghmour Jul 31 '15 at 09:17
  • I don't see how this addresses the question, which is whether my snippet is valid. You seem to be presenting a different snippet which is not. You say `b{a}` is not constexpr, but my question is whether I'm allowed to have that within a constexpr function, which I'm not sure is the same (maybe it is?). – Joseph Garvin Jul 31 '15 at 14:21
  • @ShafikYaghmour The last snippet in this answer can almost never work, and it cannot appear inside a `constexpr` function. – Potatoswatter Aug 01 '15 at 01:52
0

In a nutshell, you can't pass non-static/temporary values as references at compile time. You can pass static/global values as constexpr references. But anything else is simply not available at compile time.

constexpr void foo() {
    int a; // run-time value.
    ...
}

An obvious solution would be to pass by value. It's happening at compile time, so you might not get the usual optimizations, but it's also happening at compile time.

http://ideone.com/J7mVj5

#include <iostream>

struct A { int x; constexpr A(int i) noexcept : x{i} {} };
struct B { A a; constexpr B(A a) noexcept : a{a} {} };

constexpr int foo() { 
    B b{55};
    return b.a.x;
}

template<int N>
void output()
{
    std::cout << N << std::endl;
}

int main() {
    // to be absolutely sure compile time eval'd,
    // pass as template arg
    constexpr auto b = foo();
    output<b>();
}

See also http://ideone.com/tw4jzG

kfsone
  • 23,617
  • 2
  • 42
  • 74