#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
.