2

Take a look at the following code example:

template<bool val>
struct test {
    static const int value_a = val;
    const int value_b = val;
    constexpr int get_value_a() const noexcept { return value_a; }
    constexpr int get_value_b() const noexcept { return value_b; }
};


int main(int argc, char** argv) {
    auto t = test<true>{};
    static_assert(t.value_a);
    // static_assert(t.value_b);
    static_assert(t.get_value_a());
    // static_assert(t.get_value_b());
}

Both gcc and clang agree that this should compile, but including any of the commented out static asserts makes it invalid. For example, gcc would then produce these error messages:

error: non-constant condition for static assertion

error: the value of ‘t’ is not usable in a constant expression

note: ‘t’ was not declared ‘constexpr’

This makes perfect sense to me and is exactly what I would have thought. But I don't really know why the other two static asserts compile in the first place. What is the relevant paragraph from the standard that allows this?

In particular, how is this formalized? Is there a clearly defined difference between just using a variable, versus actually accessing its runtime value (which then would be the only forbidden thing in a constexpr context)?

Community
  • 1
  • 1
x432ph
  • 400
  • 1
  • 10
  • Relevent [cppreference page](https://en.cppreference.com/w/cpp/language/constant_expression) about constant expressions. – IlCapitano Jan 26 '20 at 00:42

2 Answers2

1

It's just the rules. Before constexpr, const variables initialised with constant expressions could be used as constant expressions themselves (Also for some compatibility with C)

From the standard [expr.const]/3:

A variable is usable in constant expressions after its initializing declaration is encountered if [...] it is a constant-initialized variable [...] of const-qualified integral or enumeration type.

This wouldn't extend to const auto t = test<true>{} because test<true> is not an integral type (You would need to have constexpr auto t = test<true>{}, as expected, following the rules of the rest of that paragraph)

Artyer
  • 31,034
  • 3
  • 47
  • 75
  • If I understand correctly, this is what allows the value_a part of t.value_a to be used there. But my main concern is about the t part. Surely t is not constant-initialized, and it's not declared constexpr. So does this mean the standard doesn't consider it as used in that expression? Because without exact definition what not usable means, I would have thought it means it can't appear in the expression at all. – x432ph Jan 26 '20 at 00:57
  • 1
    @x432ph For static members, `(E1.static_member_name)` is equivalent to something like `(static_cast(E1), std::remove_cv_ref_t::static_member_name)` (Discarding the expression). I *think* [`[expr.ref]`](http://eel.is/c++draft/expr.ref#6.1) implies this, but it's pretty vague how the expression is actually evaluated. And the discarded expression evaluating `t` isn't in the [exceptions for constant expressions](http://eel.is/c++draft/expr.const#4) – Artyer Jan 26 '20 at 01:11
0

In particular, how is this formalized?

http://eel.is/c++draft/expr.const#4.1

An expression e is a core constant expression unless the evaluation of e, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:

this ([expr.prim.this]), except in a constexpr function ([dcl.constexpr]) that is being evaluated as part of e;

Access to non-static members evaluates the this pointer. Access to a static member does not.

n314159
  • 4,990
  • 1
  • 5
  • 20
  • Ah nice, I think this is what I'm looking for, because it defines it by what happens when evaluating the expression, instead of just talking about that something appears in the expression or not. – x432ph Jan 26 '20 at 01:00