通过 CRTP 通过基类引用访问派生类的 constexpr 成员变量

问题描述

我在尝试通过 CRTP 通过基类引用访问派生类的 constexpr 成员变量时遇到错误

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();
}

错误

<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

更新 事实证明,删除参考文献中的 constexpr 是有效的。我想了解为什么会这样?

        auto &b = static_cast<Der*>(this)->arr;
        return b.size();

解决方法

非正式地,您应该想象有一个规则,即函数内定义的 constexpr 变量 需要由总是一个表达式来初始化常量表达式与调用函数的环境无关。特别是,您可以随时执行以下操作:

Base<Derived> b;
b.getSize();

在这种情况下 static_cast<Der*>(this)->arr 不是常量表达式,因为它实际上是 UB。由于这种可能性,您的函数根本无法编译,即使您可能永远不会以这种方式调用它。

您违反的实际规则是 [expr.const]/5.1。常量表达式 E 可能不会对 this 求值,除非它是通过(直接或间接)调用其中发生对 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();

这里,s.get_self() 是一个常量表达式,因为对 this 的访问只发生在 get_self() 函数内部,它是 s.get_self() 求值的一部分。但是我们不能使 result constexpr,因为如果是这样,我们就不再“计数”封闭函数;初始化步骤本身必须符合常量表达式的要求,但事实并非如此,因为对 this 的访问现在是“裸露的”。

对于您的代码,这意味着 getsize() 实际上可能返回一个常量表达式(对于那些不会触发上述 UB 的调用),但 b 不能被 constexpr。>