Тернарный оператор разных типов

Следующий фрагмент кода ведет себя по-разному под g++ 4.9.2 и clang++ 3.7.0. Какой из них правильный? Какая часть стандарта связана с этим? Спасибо.

#include <iostream>
using namespace std;

struct Base {
  Base() = default;
  Base(const Base&) = default;
  Base(Base&&) = delete;
};

struct Derived : Base {
};

int main() {
  const Base& b = true ? Derived() : Base();
}

g++ принимает его, а clang++ дает ошибку incompatible operand types ('Derived' and 'Base'). Подробнее см. Ниже.

[hidden]$ g++ -v
Using built-in specs.
COLLECT_GCC=/usr/bin/g++
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.9.2/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.9.2-20150212/obj-x86_64-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.9.2-20150212/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux
Thread model: posix
gcc version 4.9.2 20150212 (Red Hat 4.9.2-6) (GCC) 
[hidden]$ g++ -std=c++11 b.cpp 
[hidden]$ clang++ -v
clang version 3.7.0 (http://llvm.org/git/clang.git 6bbdbba8ec8a7730c68fee94363547dc2dc65b10)
Target: x86_64-unknown-linux-gnu
Thread model: posix
Found candidate GCC installation: /usr/lib/gcc/x86_64-redhat-linux/3.4.6
Found candidate GCC installation: /usr/lib/gcc/x86_64-redhat-linux/4.9.2
Selected GCC installation: /usr/lib/gcc/x86_64-redhat-linux/4.9.2
Candidate multilib: .;@m64
Candidate multilib: 32;@m32
Selected multilib: .;@m64
[hidden]$ clang++ -std=c++11 b.cpp 
b.cpp:14:24: error: incompatible operand types ('Derived' and 'Base')
  const Base& b = true ? Derived() : Base();
                       ^ ~~~~~~~~~   ~~~~~~
1 error generated.

Ответы

Ответ 1

У меня нет N3936, но N3797 §5.12 [expr.cond]/3 содержит это (внимание мое):

В противном случае, если второй и третий операнды имеют разные типы и либо имеет (возможно, cv-qualified) тип класса, либо если оба являются значениями glvalues той же категории значений и того же типа, за исключением cv-qualification, делается попытка преобразовать каждый из этих операндов к типу другого. Процесс определения того, выражение операнда E1 типа T1 может быть преобразовано в соответствии с операндом выражение E2 типа T2 определяется следующим образом:

  • Если E2 является lvalue: [удалено]
  • Если E2 является значением x: [удалено]
  • Если E2 является prvalue или если ни одно из преобразований выше, и по крайней мере один из операндов имеет (возможно, cv-qualit) Тип класса:
    • если E1 и E2 имеют тип класса, а базовые типы классов одинаковы или - один из базовых классов другой:
      E1 можно преобразовать в соответствие с E2, если класс T2 одинаковый тип как или базовый класс, класс T1 и cv-квалификация из T2 является той же самой cv-квалификацией, что и более высокая cv-квалификация чем, cv-квалификация T1. Если применяется преобразование, E1 является изменен на prvalue типа T2 с помощью copy-initializing временной введите T2 из E1 и используйте это временное значение как преобразованный операнд.

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

Теперь, чтобы скопировать инициализацию конечного операнда Base из Derived(), мы можем посмотреть в §13.3.1.3 [over.match.ctor]:

Когда объекты типа класса имеют прямую инициализацию (8.5) или копировать-инициализировать из выражения того же или производного класса тип (8.5), разрешение перегрузки выбирает конструктор. Для прямая инициализация, все функции кандидата конструкторы класса инициализируемого объекта. Для копирование-инициализация, все функции-кандидаты преобразуются конструкторы (12.3.1) этого класса. Список аргументов - это выражение-список или выражение-присваивание инициализатора.

Преобразование конструкторов определяется в §12.3.1 [class.conv.ctor] следующим образом:

Конструктор, объявленный без явного указателя функции указывает преобразование из типов его параметров в тип его классом. Такой конструктор называется конструктором преобразования.

Теперь, если вы поверите мне (ради того, чтобы не указывать больше, чем у меня 13.3), что prvalue Derived() приведет к разрешению перегрузки, чтобы выбрать конструктор перемещения (принимая Base&&), несмотря на удаление, это вызывает ошибку у Clang.

В заключение, Clang правилен при выдаче ошибки. Поскольку использование удаленной функции требует диагностики, это ошибка в GCC.