C++ 从三元运算符的一侧移出

C++ 从三元运算符的一侧移出,c++,c++11,gcc,clang,C++,C++11,Gcc,Clang,我正在编写一些类似以下内容的代码: std::string foo(bool b, const std::string& fst, std::string&& snd) { return b ? fst : std::move(snd); } 然后叮当一声,将snd复制出去,而gcc将其移出。 我试图尽量减少这个例子,我想到了: #include <iostream> #include <utility> struct printer {

我正在编写一些类似以下内容的代码:

std::string foo(bool b, const std::string& fst, std::string&& snd) {
  return b ? fst : std::move(snd);
}
然后叮当一声,将
snd
复制出去,而
gcc
将其移出。 我试图尽量减少这个例子,我想到了:

#include <iostream>
#include <utility>

struct printer {
  printer() { }
  printer(const printer&) { std::cout << "copy" << std::endl; }
  printer(printer&&) { std::cout << "move" << std::endl; }
  printer(const printer&&) { std::cout << "const rvalue ref" << std::endl; }
};

int main() {
  const printer fst;
  printer snd;
  false ? fst : std::move(snd);
}
clang 3.6输出

const rvalue ref
该标准是否同时允许gcc和叮当行为

随机观察结果如下:

gcc和clang都将三元的类型统一为:

const printer

std::move(x) 好的,让我们首先计算一下std::move(snd)的类型。根据§20.2.4:

template constexpr remove\u reference\u t&&move(t&&t)noexcept

返回:
static\u cast(t)

根据§5.2.9/1:

表达式
static_cast(v)
的结果是将表达式
v
转换为类型
T
的结果。如果
T
是左值引用类型或函数类型的右值引用,则结果为左值如果
T
是对对象类型的右值引用,则结果为xvalue
;否则,结果是一个pr值。
static_cast
操作员不得丢弃常量(5.2.11)

(强调矿山)

好的,
std::move(snd)
的返回值是
printer&
类型的xvalue。
fst
的类型是
const printer
类型的左值


如何计算常用类型 现在,标准描述了计算三元条件运算符的结果表达式类型的过程:

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

  • 如果E2是左值:如果E1可以隐式转换(第4条)为类型“对T2的左值引用”,则E1可以转换为与E2匹配的值,但必须遵守以下约束,即在转换过程中引用必须直接绑定(8.5.3)到左值
  • 如果E2是xvalue:如果E1可以隐式转换为类型“rvalue reference to T2”,则可以将E1转换为与E2匹配的值,但必须遵守引用必须直接绑定的约束
  • 如果E2是prvalue,或者上述两种转换都无法完成,并且至少有一个操作数具有(可能是cv限定的)类类型:

    • 如果E1和E2具有类类型,且基础类类型相同或其中一个是另一个的基类:如果T2的类与T1的类相同或基类相同,且T2的cv鉴定与T1的cv鉴定相同,或cv鉴定大于,T1的简历资格。如果应用了转换,则通过从E1复制初始化T2类型的临时值并将该临时值用作转换后的操作数,将E1更改为T2类型的prvalue
    • 否则(如果E1或E2具有非类类型,或者如果它们都具有类类型,但基础类不相同,也不是另一个的基类):如果E1可以隐式转换为E2在应用左值后将具有的类型,则可以将E1转换为与E2匹配的类型- 右值(4.1)、数组到指针(4.2)和函数到指针(4.3)的标准转换
使用此过程,确定是否可以将第二个操作数转换为与第三个操作数匹配,,以及是否可以将第三个操作数转换为与第二个操作数匹配。如果两者都可以转换,或者其中一个可以转换但转换不明确,则程序的格式不正确。如果两者都不能转换,则操作数保持不变,并按如下所述执行进一步检查。如果只能进行一次转换,则该转换将应用于选定的操作数,并在本节剩余部分使用转换后的操作数代替原始操作数

(再次强调我的)


在这方面 因此,我们有两种情况:

  • E1是fst,E2是std::move(snd)
  • E1是标准移动(snd),E2是标准移动(fst)
  • 在第一种情况下,E2是一个x值,因此:

    如果E2是xvalue:如果E1可以隐式转换为类型“rvalue reference to T2”,则可以将E1转换为与E2匹配的值,但必须遵守引用必须直接绑定的约束

    适用;但是E1(类型为
    常量打印机
    )无法隐式转换为
    打印机&
    ,因为它将丢失常量。所以这种转换是不可能的

    在第二种情况下,我们有:

    如果E2是左值:如果E1可以隐式转换(第4条)为类型“对T2的左值引用”,则E1可以转换为与E2匹配的值,但必须遵守以下约束,即在转换过程中引用必须直接绑定(8.5.3)到左值

    适用,但E1(
    std::move(snd)
    类型的
    printer&
    )可以隐式转换为
    const printer&
    ,但不直接绑定到左值;所以这个也不适用

    目前,我们正处于:

    如果E2是prvalue,或者上述两种转换都无法完成,并且至少有一个操作数具有(可能是cv限定的)类类型:

    我们必须从中考虑:

    如果E1和E2具有类类型,并且基础类类型相同,或者其中一个是另一个的基类:如果T2的类与cl的类型相同,或者是cl的基类,则可以将E1转换为与E2匹配
    const printer