Почему эта константная функция-член constexpr не рассматривается как constexpr при вызове?
Почему эта функция-член constexpr
static
, идентифицированная комментарием //! Nah
, не отображается как constexpr
при вызове?
struct Item_id
{
enum Enum
{
size, position, attributes, window_rect, max_window_size, _
};
static constexpr int n_items_ = _; // OK
constexpr auto member_n_items() const -> int { return _; } // OK
static constexpr auto static_n_items() -> int { return _; } // OK
static constexpr int so_far = n_items_; // OK
#ifndef OUT_OF_CLASS
static constexpr int bah = static_n_items(); //! Nah.
#endif
};
constexpr auto n_ids() -> int { return Item_id().member_n_items(); } // OK
auto main() -> int
{
#ifdef OUT_OF_CLASS
static constexpr int bah = Item_id::static_n_items(); // OK
#endif
}
Отчеты MinGW g++ 5.1
constexpr.cpp:12:46: error: 'static constexpr int Item_id::static_n_items()' called in a constant expression
static constexpr int bah = static_n_items(); //! Nah.
Отчеты Visual С++ 2015
constexpr.cpp(12): error C2131: expression did not evaluate to a constant
constexpr.cpp(12): note: failure was caused by call of undefined function or one not declared 'constexpr'
constexpr.cpp(12): note: see usage of 'Item_id::static_n_items'
Мой текстовый редактор настаивает на том, что имя в вызове совпадает с именем в определении функции.
Кажется, что-то связано с неполным классом, потому что с помощью OUT_OF_CLASS
он компилирует красиво.
Но тогда почему работают данные n_items_
и почему такое правило (не имеет смысла для меня)?
Ответы
Ответ 1
Из памяти тела членов-членов оцениваются только после того, как класс полностью определен.
static constexpr int bah = static_n_items();
формирует часть определения класса, но ссылается на (статическую) функцию-член, которая еще не может быть определена.
Решение:
отложить постоянные выражения к базовому классу и извлечь из него.
например:.
struct Item_id_base
{
enum Enum
{
size, position, attributes, window_rect, max_window_size, _
};
static constexpr int n_items_ = _; // OK
constexpr auto member_n_items() const -> int { return _; } // OK
static constexpr auto static_n_items() -> int { return _; } // OK
static constexpr int so_far = n_items_; // OK
};
struct Item_id : Item_id_base
{
#ifndef OUT_OF_CLASS
static constexpr int bah = static_n_items(); // now OK
#endif
};
constexpr auto n_ids() -> int { return Item_id().member_n_items(); } // OK
auto main() -> int
{
#ifdef OUT_OF_CLASS
static constexpr int bah = Item_id::static_n_items(); // OK
#endif
}
Почему, по вашему мнению, стандарт запрещает это?
Потому что это незаконно:
struct Item_id
{
// ... etc.
#ifndef OUT_OF_CLASS
static constexpr int bah;// = static_n_items(); //! Nah.
#endif
};
constexpr int Item_id::bah = static_n_items();
И constexpr должно иметь определение constexpr. Единственное, что мы можем определить, это его объявление...
... поэтому по вычету он не может ссылаться на какую-либо функцию, тело которой еще не определено.
Я затрудняюсь узнать, где искать в стандарте все это. Вероятно, 5 разных, казалось бы, несвязанных статей:)
Ответ 2
[class.mem]/2
В классе-член класса класс считается полным в телах функций, аргументах по умолчанию, спецификациях исключений и инициаторах элементов по умолчанию (включая такие вещи во вложенных классах). В противном случае он считается неполным в пределах своей спецификации класса.
В инициализаторе элемента данных static
класса, класс является неполным. Инициализатор может видеть только объявления участников, которые предшествуют ему, и любые функции-члены, которые он может видеть, считаются объявленными, но не определенными. Вызов функции, объявленной, но не определенной, не может быть постоянным выражением.