Это ошибка? Конструктор Constexpr бесшумно становится неконференциальным

Посмотрите на этот код:

struct NonConstexpr {
    NonConstexpr() { }
};

template <typename T>
struct Bar {
    NonConstexpr nonConstexpr;

    constexpr Bar() { }
};

struct Foo {
    Bar<void> bar;

    constexpr Foo() { }
};

Foo имеет член, Foo::bar::nonConstexpr, который имеет конструктор non-constexpr. Итак, я ожидаю, что это не должно компилироваться. Но он компилируется с помощью gcc, clang и msvc. Является ли это ошибкой компилятора, или какое-то правило позволяет этот код компилироваться?

Если я добавлю член NonConstexpr в Foo напрямую, код больше не компилируется.

(У меня возникла эта проблема, потому что я ожидал статической инициализации для глобального объекта Foo, но он получил динамическую инициализацию и вызвал проблему из-за "статического фиаско порядка инициализации")

Ответ 1

Является ли это ошибкой компилятора, или какое-то правило позволяет этот код компилироваться?

Правило, которое позволяет компилировать это:

10.1.5 Спецификация constexpr [dcl.constexpr]
...
6. Если экземпляр шаблон специализация constexpr шаблон функции или член функции шаблона класса будет не в состоянии удовлетворить требования, предъявляемые к constexpr функции или constexpr конструктора, что специализация по - прежнему constexpr функция или constexpr конструктор, хотя вызов таких функция не может появляться в постоянном выражении. Если никакая специализация шаблона не удовлетворяет требованиям для функции constexpr или конструктора constexpr при рассмотрении как не-шаблонной функции или конструктора, шаблон плохо сформирован, не требуется диагностика.

Вышеприведенная цитата взята из стандартного черновика CP47 N4713.


Из цитаты не может быть ясно, как Bar<void> Конструктор может появиться в Foo конструктору Foo конструктор constexpr. Но, как отмечено в комментариях, constexpr не совпадает с постоянным выражением. Конструктор Foo не является выражением, а тем более постоянным выражением.

Ответ 2

Я ожидал статической инициализации для глобального объекта Foo

Вероятно, вы забыли указать конструктор constexpr для этого глобального объекта Foo, чтобы компилятор не требовал, чтобы конструктор был constexpr.

Если вы укажете constexpr, который не будет компилироваться.


могу ли я каким-то образом (время компиляции) определить, что Foo :: Foo() является constexpr или нет? Поэтому я могу поставить static_assert для него.

Вы можете:

Поскольку оператор noexcept всегда возвращает true для константного выражения, его можно использовать для проверки того, принимает ли конкретный вызов функции constexpr постоянного выражения.

static_assert(noexcept(Foo{}), "Foo{} is not constexpr.");