不同类型的三元运算符

ternary operator of different types

本文关键字:三元 运算符 同类型      更新时间:2023-10-16

以下代码在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++给出错误CCD_ 1。有关详细信息,请参见下文。

[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.

我手头没有N3936,但N3797§5.12[expr.cond]/3包含了这个(强调我的):

否则,如果第二个和第三个操作数具有不同的类型,并且要么具有(可能是cv限定的)类类型,要么两者都是glvalues除了cv限定,尝试转换这些操作数中的每一个另一个的类型。确定T1类型的操作数表达式E1可以转换为与操作数匹配T2类型的表达式E2定义如下:

  • 如果E2是左值:[已删除]
  • 如果E2是xvalue:[已删除]
  • 如果E2是prvalue,或者如果两个转换都不是可以完成以上操作,并且至少有一个操作数(可能cv合格)类别类型:
    • 如果E1和E2具有类类型,并且基础类类型相同,或者其中一个是其他
      如果T2的类别相同,则可以将E1转换为匹配E2键入as、或基类、类T1和cv资格T2是与相同的cv资格,或更高的cv资格比T1的cv资格。如果应用转换,则E1为通过复制初始化临时从E1键入T2,并使用该临时操作数作为转换后的操作数

使用此过程,可以确定第二个操作数是否可以转换为匹配第三个操作数,以及第三个运算数可以转换为匹配第二个操作数。如果两者都可以或者一个可以被转换但转换是不明确的,这个程序格式不正确。如果两者都不能转换,则操作数保持不变,并按照说明进行进一步检查下面如果只可能进行一次转换,则该转换为应用于选定的操作数,转换后的操作数用于此节剩余部分的原始操作数的位置

现在,要从Derived()复制初始化最终的Base操作数,我们可以查看§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中的一个错误。