Почему эта константная функция-член 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 класса, класс является неполным. Инициализатор может видеть только объявления участников, которые предшествуют ему, и любые функции-члены, которые он может видеть, считаются объявленными, но не определенными. Вызов функции, объявленной, но не определенной, не может быть постоянным выражением.