доступ к переменной-члену constexpr производного класса через ссылку на базовый класс через CRTP

#c #templates #c 14 #crtp

#c #шаблоны #c 14 #crtp

Вопрос:

Я сталкиваюсь с ошибкой при попытке получить доступ constexpr к переменной-члену производного класса через ссылку на базовый класс через CRTP;

 template <typename Der>
struct Base
{
    constexpr std::size_t getsize()
    {
        constexpr const auto amp;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 amp;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 amp;b = static_cast<Der*>(this)->arr;
                                                    ^
1 error generated.
Compiler returned: 1
 

Обновить
Оказывается, что удаление constexpr в ссылке работает. Я хотел бы понять, почему это работает?

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

Комментарии:

1. Вы могли бы использовать Der::arr .

2. Все сводится к тому, что считается константой времени компиляции, а что нет. В контексте Base компилятор не имеет возможности узнать, static_cast<Der*>(this)->arr; действительно ли это постоянное выражение, поскольку оно зависит от производного класса.

Ответ №1:

Неофициально, вы должны представить, что существует правило, согласно которому 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 resu<
    }
};
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 .