Тернар разрешен неявным вызовом явного конструктора копии?

Рассмотрим следующий код:

#include <cstdio>
struct A
{
    A(){}
    explicit A(const A&) {std::puts("copy");}
};

int main() 
{
    A a;
    true ? a : A();
    return 0;
}

Поскольку я понимаю, что троянец попытается скопировать a и должен потерпеть неудачу, поскольку конструктор копирования является явным, однако gcc компилирует это как раз и создает копию. Клэнг выплевывает ошибку, как ожидалось.

Это ошибка в gcc?

Я использую gcc 8.1 и clang 7.0 в режиме С++ 17, но я также пробовал все версии gcc в проводнике компилятора в режиме С++ 98, и все они ведут себя одинаково.

Ответ 1

Кланг прав, чтобы отвергнуть его, и это действительно ошибка GCC. Я приведу n4659 (ближайший документ, который я должен использовать для стандарта C++ 17) для простоты.

Прежде всего, тип условного выражения в вашем примере, указанный в [expr.cond] ¶6, должен быть значением класса A

Теперь, согласно [expr.cond] ¶7, внимание мое:

Стандартные преобразования Lvalue-to-rvalue, array-to-pointer и standard-to-pointer выполняются во втором и третьем операндах.

a должен иметь возможность пройти преобразование lvalue-to-rvalue. Который для a указан в [conv.lval] ¶3.2 (опять же, мой удар) как

В противном случае, если T имеет тип класса, копия преобразования - инициализирует объект результата из glvalue.

Копирование инициализации A из A в любом контексте должно выбрать конструктор преобразования в разрешении перегрузки ([over.match.copy] ¶1.1):

Конструкторы преобразования T являются функциями-кандидатами.

И явный конструктор копирования не является конструктором преобразования ([class.conv.ctor] ¶3)

Конструктор без явного копирования/перемещения ([class.copy]) является преобразование конструктора.

Соответствующая реализация C++ не может принять условное выражение, которое вы написали как хорошо сформированное.