Reputation: 1534
I'm running into an error when trying to access a constexpr
member variable of a derived class through a base class reference via CRTP;
template <typename Der>
struct Base
{
constexpr std::size_t getsize()
{
constexpr const auto &b = static_cast<Der*>(this)->arr;
return b.size();
//return static_cast<Der*>(this)->arr.size(); // this works
}
};
struct Derived : Base<Derived>
{
static constexpr std::array<int, 10> arr = {};
};
int main(){
Derived d;
return d.getsize();
}
Error:
<source>:11:31: error: constexpr variable 'b' must be initialized by a constant expression
constexpr const auto &b = static_cast<Der*>(this)->arr;
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:24:14: note: in instantiation of member function 'Base<Derived>::getsize' requested here
return d.getsize();
^
<source>:11:53: note: use of 'this' pointer is only allowed within the evaluation of a call to a 'constexpr' member function
constexpr const auto &b = static_cast<Der*>(this)->arr;
^
1 error generated.
Compiler returned: 1
Update It turns out that removing the constexpr in the reference works. I'd like to understand why this works ?
auto &b = static_cast<Der*>(this)->arr;
return b.size();
Upvotes: 3
Views: 1207
Reputation: 119239
Informally, you should imagine there being a rule that a constexpr
variable defined inside a function is required to be initialized by an expression that is always a constant expression regardless of the circumstances under which the function is called. In particular, you can always do something like this:
Base<Derived> b;
b.getSize();
in which case static_cast<Der*>(this)->arr
is not a constant expression, since it is in fact UB. Because of the possibility of things like this, your function can't compile at all even though you may never call it in this manner anyway.
The actual rule you are violating is [expr.const]/5.1. A constant expression E may not evaluate this
, unless it's by (directly or indirectly) calling some member function inside of which the evaluation of this
occurs. In particular, this means that if we have some code like this:
// namespace scope
struct S {
constexpr const S* get_self() {
const S* result = this;
return result;
}
};
constexpr S s;
constexpr const S* sp = s.get_self();
Here, s.get_self()
is a constant expression, because the access to this
only occurs inside the get_self()
function, which is part of the evaluation of s.get_self()
. But we cannot make result
constexpr
, because if it were so, we no longer get to "count" the enclosing function; the initialization step would have to qualify in and of itself as a constant expression, which it is not, since the access to this
is now "bare".
For your code this implies that getsize()
actually may return a constant expression (for those calls that do not trigger UB as described above) but b
cannot be made constexpr
.
Upvotes: 2