Подтвердить что ты не робот

Вложенные struct breaks constexpr, несмотря на то, что они идентичны глобальным

У меня возникают проблемы со следующим кодом:

template<typename T>
constexpr int get(T vec) {
  return vec.get();
}

struct coord {
  constexpr int get() const { return x; }
  int x;
};

struct foo {
    struct coord2 {
      constexpr int get() const { return x; }
      int x;
    };
    constexpr static coord f = { 5 };
    constexpr static int g = get(f); // works

    constexpr static coord2 h = { 5 };
    constexpr static int i = get(h); // doesn't work
};

constexpr coord foo::f;
constexpr foo::coord2 foo::h;

int main(){}

По существу, get(f) считается постоянным выражением, но get(h) не является. Единственное, что изменилось, это использование глобальной структуры coord, в то время как другая использует вложенную структуру coord2. Структуры,  тела идентичны.

Почему это?


Ошибка GCC:

test.cpp:20:35: error: field initializer is not constant

Ошибка Clang:

test.cpp:20:26: error: constexpr variable 'i' must be initialized by a constant expression
    constexpr static int i = get(h); // doesn't work
                         ^   ~~~~~~
test.cpp:8:10: note: undefined function 'get' cannot be used in a constant expression
  return vec.get();
         ^
test.cpp:20:30: note: in call to 'get({5})'
    constexpr static int i = get(h); // doesn't work
                             ^
test.cpp:13:21: note: declared here
      constexpr int get() const { return x; }
4b9b3361

Ответ 1

Это постоянное выражение... в конце концов, поскольку это показывает, вы можете видеть, перемещая i в main():

В сообщениях об ошибках довольно ясно, что происходит, а это значит, что foo::coord2::get() еще не определен, поскольку определения функций-членов задерживаются до конца охватывающего класса, чтобы они могли использовать объявленные позже члены.

Немного удивительно, что определение задерживается до конца внешнего охватывающего класса, но вы были бы еще более удивлены, если foo::coord2::get() не смог получить доступ к foo::g.

Стандарт согласуется с компилятором, кстати. В разделе 9.2p2 говорится:

В классе-член класса класс считается полным внутри тел функции, аргументы по умолчанию, спецификации исключений и шаблонные или равные-инициализаторы для нестатических элементов данных (включая такие вещи во вложенных классах).

К сожалению, только вывод о том, что закрывающая скобка объявления класса становится точкой определения для этих отложенных регионов. Я считаю, что это дефект в Стандарте, что он не говорит об этом явно.

См. также: