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

Нечетное поведение, передающее статические члены constexpr без определений по значению

Я с удивлением обнаружил, что GCC и Clang не согласны с тем, чтобы дать мне ошибку компоновщика при передаче статического члена constexpr по значению, если нет определения вне класса:

#include <iostream>
#include <type_traits>
#include <typeinfo>

template <typename X>
void show(X)
{
    std::cout << typeid(X).name() << std::endl;
}

template <typename T>
struct Foo
{
    //static constexpr struct E {} nested {}; // works in gcc and clang
    //static constexpr struct E {T x;} nested {}; // doesn't work in gcc
    //static constexpr enum E {} nested {}; // works in gcc and clang
    //static constexpr enum E { FOO } nested {}; // works in gcc and clang
    //static constexpr struct E { constexpr E() {} constexpr E(const E&) {} T x=T();} nested {}; // works in gcc and clang
    static constexpr struct E { constexpr E() {} constexpr E(const E&) = default; T x=T(); } nested {}; // doesn't work in gcc
};

int main()
{
    Foo<int> x;
    show(x.nested);
}

Фрагмент можно воспроизвести с помощью здесь.

Я хотел бы использовать синтаксис первой строки без определения вне класса:

static constexpr struct E {} nested {}; // works in gcc and clang

Если в E нет членов, Clang и GCC, похоже, не заботятся о том, чтобы у меня не было определения класса nested, если я отключу ODR (например, взяв адрес). Является ли этот стандарт обязательным или удачным?

Когда есть члены, GCC (5.2), похоже, дополнительно хочет, чтобы я вручную определил конструктор экземпляра constexpr. Является ли это ошибкой?

От googling и SO я нашел несколько разных ответов, но трудно разоблачать, которые обновлены с С++ 14. В С++ 98/03 я считаю, что внутри класса могут быть инициализированы только целые типы. Я думаю, что С++ 14 расширил это до "литерала", который включает в себя Constexpr конструктивные вещи. Я не знаю, если это то же самое, что сказать, что мне разрешено уйти с отсутствием определения вне класса.

4b9b3361

Ответ 1

Итак, в тех случаях, когда E является классом, все они выглядят как нарушения odr, если мы смотрим на странице cppreferences на odr-use it говорит:

Неофициально, объект используется odr, если его адрес занят, или ссылка привязана к нему, а функция используется odr, если выполняется вызов функции или ее адрес. Если объект или функция используется odr, его определение должно существовать где-то в программе; нарушением которой является ошибка времени соединения.

и в этом случае мы берем ссылку, когда мы вызываем конструктор копирования в этой строке:

show(x.nested);

Также стоит отметить, что odr-нарушения не требуют диагностики.

Похоже, что вы видите в некоторых случаях эффекты конструктора elision с gcc, если мы используем - fno-elide-constructors, мы получить ошибку для всех случаев, когда E - класс. В случаях перечисления применяется преобразование lvalue-rvalue и, следовательно, нет использования odr.

Обновить

dyp указал мне на отчет о дефекте 1741, в котором задается вопрос о том, является ли привязка к ссылочному параметру копии ctor нецелевым использованием или нет:

Использует ли этот odr-использование T:: s, требуя, чтобы он имел определение, из-за привязки его к эталонному параметру конструктора S-копии?

и результатом было следующее изменение в пункте 3 [basic.def.odr]:

Переменная x, имя которой отображается как потенциально оцениваемое выражение ex, используется odr, если x не удовлетворяет требованиям к появлению в константном выражении (5.20 [expr.const]) , применяя преобразование lvalue-to-rvalue (4.1 [conv.lval]) в x дает константное выражение (5.20 [expr.const]), которое не вызывает никаких нетривиальных функций и, если x - объект, ex - элемент множества потенциальных результатов выражения e, где либо преобразование lvalue-to-rval (4.1 [conv.lval]) применяется к e, либо e является выражением отбрасываемого значения (пункт 5 [expr ]). это используется odr...

Итак, возникает вопрос, какие случаи охватываются этим изменением. Казалось бы, эти примеры в порядке:

//static constexpr struct E {} nested {}; // works in gcc and clang
//static constexpr struct E {T x;} nested {}; // doesn't work in gcc
static constexpr struct E { constexpr E() {} constexpr E(const E&) = default; T x=T(); } nested {}; // doesn't work in gcc

Так как копия ctor тривиальна, а эта не является тривиальной:

//static constexpr struct E { constexpr E() {} constexpr E(const E&) {} T x=T();} nested {}; // works in gcc and clang

Мы можем подтвердить это, используя std:: is_trivially_copyable, посмотреть он живет.

Дела перечисления по-прежнему в порядке по тем же причинам, которые я изначально заявил.

Дефект также сообщает о разнице в реализации.