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
. Если они это сделают, компилятор должен его принять. Если они этого не делают, компилятор должен диагностировать проблему.