1

When I declare a template class, I'm able to use any non-type template arguments in constexpr member functions, such as this:

template <int Value>
struct Foo {
    constexpr int value() const { return Value; }
};

I can then use this function later to initialize constexpr variables like so:

template <int Value>
void use_value(Foo<Value> foo) {
    constexpr int baz{ foo.value() };
}

int main() {
    Foo<10> bar;
    use_value(bar);
}

What I don't understand is that when I take foo by reference...

template <int Value>
void use_value(Foo<Value>& foo) {       // by ref
    constexpr int baz{ foo.value() };
}

int main() {
    Foo<10> bar;
    use_value(bar);
}

...clang will tell me that foo.value() is not a constant expression!

test.cpp:8:24: error: constexpr variable 'baz' must be initialized by a constant expression
    constexpr int baz{ foo.value() };
                     ~~^~~~~~~~~~~~~
test.cpp:13:5: note: in instantiation of function template specialization 'use_value<10>'
      requested here
    use_value(bar);

Why does this happen? Has it got something to do with fact that in the first example the compiler was able to follow the entire lifetime of foo, whereas when taken by reference, some other part of the program could alter the instance, somehow affecting the constexpr-ness of foo.value()? Although it's clearly not the case here (given the template specialization, there is no way of changing Value's value), I'm not sure if you could indeed "ruin" constexpr member functions by tampering with the instance in the middle of its execution in some cases.

Edit

Okay, so references are bad for constexpr, since they have a runtime location - and constexpr values are hardcoded in the resulting executable. How about taking a constant reference? This works:

int main() {
    constexpr int foo2{5};
    int const& bar2{ foo2 };
}

But with the Foo class, it won't help to change the reference to const:

template <int Value>
void use_value(Foo<Value> const& foo) {    // const ref
    constexpr int baz = foo.value();
}

I get the same compiler error. What is the difference here?

user1685094
  • 57
  • 1
  • 5
  • If `Foo::value` is supposed to return `Value`, you should just make it and then use this static function in `use_value` (and throw away the parameter foo completely). – Rumburak Jan 21 '16 at 20:33
  • I agree, this is just a silly example to demonstrate what I mean. It does not try to make sense programming-wise. – user1685094 Jan 21 '16 at 20:40
  • Well, in the end, a reference is just a pointer with slightly different semantics. For `constexpr`, you need values... – Rumburak Jan 21 '16 at 20:52

2 Answers2

1

Something passed by reference cannot be constexpr. And, for a function to actually be used as constexpr all of the arguments and objects used within the function must also be constexpr. Now, since a reference (even of a template object) can never be constexpr, this means that the better choice is for the compiler to throw an error and let the developer know, rather than the alternative which is to generate the non-constexpr version of the function.

This is because reference means it must have a runtime memory location (see C++11 rvalue and lvalue, and rvalue references). If something qualifies as constexpr it means it is only known and used at compile time, and the result is baked into the resulting executable the same way as hardcoded numbers or strings.

EDIT:

A constant reference (const&) is still a reference. const& acts as an argument qualifier similar to a regular reference and still requires a runtime memory location - const& objects must 'reference' another object. See What is a constant reference? (not a reference to a constant)

In your example, the const& is dependent on the constexpr which is fine. If the constexpr were dependent on a const& it would not work.

Community
  • 1
  • 1
Alex Court
  • 148
  • 11
  • In this case, it's not the supposedly constexpr value that is passed by reference, just the Foo instance that contains the code for "computing" it. I can't see how that affects the actual function call, since like you said, the constant 10 should be "baked into the executable" and is in fact, in a sense, passed by value in the template parameter. – user1685094 Jan 21 '16 at 20:02
  • Also, can I not take a reference of a static variable, which does not have a runtime memory location? Same applies to any constexpr variable, does it not? – user1685094 Jan 21 '16 at 20:05
  • 1
    See revision, and yes static variables do in fact have a runtime memory location - but just one instead of one per instance. If they did not have a runtime memory location you could not change static variables ever. – Alex Court Jan 21 '16 at 20:08
  • Okay, I understand now. How about taking Foo by a constant reference? I can at least initialize const references with constexpr variables. Editing the question... – user1685094 Jan 21 '16 at 20:16
  • If what you mean is 'const Foo& foo' then this will not work either - this only means that you won't be changing foo inside the function. – Alex Court Jan 21 '16 at 20:26
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/101339/discussion-between-user1685094-and-alex-court). – user1685094 Jan 21 '16 at 20:41
0

Consider why you are doing what you are doing. Why do you want to pass a constexpr by reference in the first place? There is no benefit, and it is actually meaningless.

Additionally, int const& is not a constant reference to an int but rather a reference to a const int. So when you right code like this int const& bar2{ foo2 } then it works. There is actually no such thing as a 'constant reference' (but as a side note there are constant pointers: int * const).

So in summary, do not do Foo<Value>& foo as there is no reason to do so. Foo<Value> foo does exactly what you want and evaluates during compile time.

AOK
  • 493
  • 5
  • 16