Reputation: 57
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?
Upvotes: 1
Views: 211
Reputation: 503
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.
Upvotes: 0
Reputation: 158
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.
Upvotes: 1