GCC и Clang разные поведения в конструкторе constexpr

Для этой структуры:

struct Wrapper {
    int value;

    constexpr explicit Wrapper(int v) noexcept : value(v) {}
    Wrapper(const Wrapper& that) noexcept : value(that.value) {}
};

И эта функция:

constexpr Wrapper makeWrapper(int v)
{
    return Wrapper(v);
}

Следующий код не удается скомпилировать для Clang (Apple LLVM версии 7.3.0), но компилируется для GCC (4.9+), как с -Wall -Wextra -Werror -pedantic-errors:

constexpr auto x = makeWrapper(123);

Clang жалуется, что "конструктор" не-constexpr "Wrapper" не может использоваться в постоянном выражении". Какой компилятор прав?

Ответы

Ответ 1

Хотя копирование или перемещение при возврате Wrapper из makeWrapper() может быть отменено, оно должно существовать с С++ 14. Существующий конструктор копирования не является constexpr, и его существование препятствует созданию неявного конструктора перемещения. В результате я думаю, что clang прав: вам нужно создать конструктор копирования constexpr.

Обратите внимание, что с С++ 17 код может стать правильным: есть предложение сделать обязательным копирование в некоторых контекстах: P0135r0. Однако, похоже, это изменение еще не приземлилось в рабочем документе. Он может приземлиться на этой неделе, хотя (спасибо @NicolBolas за указание, что его еще нет). Я не видел обновленную бумагу в mailing.

Ответ 2

Кланг верен. Он работает в g ​​++, потому что он автоматически переходит в конструктор копирования (RVO). если вы пройдете -fno-elide-constructors. g++ также будет жаловаться.

В стандарте С++ 14 не ясно о копировании-Elision в объектах constexpr.

[class.copy/32]... (частично воспроизведено здесь)

Когда критерии для выполнения операции копирования/перемещения выполняются........ выбранный конструктор должен быть доступен, даже если вызов опущены.

До тех пор, пока мы не узнаем определение accessible? Можно предположить, что g++ также верна?

dcl.constexpr/9

Спецификатор constexpr, используемый в объявлении объекта, объявляет объект как const. Такой объект должен иметь буквальный тип и должен быть инициализируется. Если он инициализируется вызовом конструктора, этот вызов должно быть постоянным выражением ([expr.const]). В противном случае, или если Спецификатор constexpr используется в описании ссылки, каждый полное выражение, которое появляется в его инициализаторе, должно быть константой выражение.

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

Демо:

struct Wrapper {
    int value;

    constexpr explicit Wrapper(int v) noexcept : value(v) {}
    Wrapper(const Wrapper& that) noexcept : value(that.value)  {}
};

constexpr Wrapper makeWrapper(int v)
{
    return Wrapper(v);
}

int main()
{
    constexpr auto x = makeWrapper(123);
}

Скомпилировать с помощью

g++ -std=c++14 -Wall -pedantic -fno-elide-constructors main.cpp && ./a.out

Смотрите живите здесь

Ответ 3

Оба компилятора правы.

Правила для функций constexpr и инициализаторы говорят, что не может быть вызвана функция не constexpr.

Правила для копирования elision говорят, что он не указан, вызывает ли конструктор копии не constexpr.

Единственный вывод может заключаться в том, что он не определил, соответствуют ли функция и инициализатор требованиям constexpr. Если они это сделают, компилятор должен его принять. Если они этого не делают, компилятор должен диагностировать проблему.