Nic*_*ick 12 c++ gcc clang c++11
我写的代码类似于:
std::string foo(bool b, const std::string& fst, std::string&& snd) {
return b ? fst : std::move(snd);
}
Run Code Online (Sandbox Code Playgroud)
把它扯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);
}
Run Code Online (Sandbox Code Playgroud)
gcc 5.2输出
move
Run Code Online (Sandbox Code Playgroud)
clang 3.6输出
const rvalue ref
Run Code Online (Sandbox Code Playgroud)
标准是否同时允许gcc和clang行为?
随机观察如下:
gcc和clang都将三元的类型统一为:
const printer
Run Code Online (Sandbox Code Playgroud)
Sho*_*hoe 11
std::move(x)好吧,让我们首先弄清楚它的类型std::move(snd).根据§20.2.4,实现std::move(x)被大致定义为static_cast<T&&>(x):
template <class T> constexpr remove_reference_t<T>&& move(T&& t) noexcept;返回:
static_cast<remove_reference_t<T>&&>(t).
根据§5.2.9/ 1:
表达式
static_cast<T>(v)的结果是将表达式转换v为type 的结果T.如果T是左值引用类型或函数类型的右值引用,则结果为左值; ifT是对象类型的右值引用,结果是xvalue ; 否则,结果是prvalue.该static_cast经营者不得抛弃常量性(5.2.11).
(强调我的)
好的,所以返回的值std::move(snd)是类型的xvalue printer&&.类型fst是左值类型const printer.
现在,该标准描述了计算三元条件运算符的结果表达式类型的过程:
如果第二个和第三个操作数具有不同的类型并且具有(可能是cv限定的)类类型,或者两者都是相同值类别的glvalues和除cv-qualification之外的相同类型,则尝试转换每个操作数操作数与另一种类型的操作数.确定类型T1的操作数表达式E1是否可以转换为匹配类型T2的操作数表达式E2的过程定义如下:
- 如果E2是左值:如果E1可以被隐式转换(第4条)到类型"左值引用T2",则E1可以被转换为匹配E2,受制于转换中引用必须直接绑定的约束(8.5.3) )到左值.
- 如果E2是x值:如果E1可以隐式转换为"rvalue reference to T2"类型,则E1可以转换为匹配E2,受限于引用必须直接绑定.
如果E2是prvalue,或者上面的转换都不能完成,并且至少有一个操作数具有(可能是cv-qualified)类类型:
- 如果E1和E2具有类类型,并且底层类类型相同或者一个是另一个类的基类:如果T2的类与类的类型相同,则可以转换为E1,或者基类类, T1的类别和T2的cv资格是与cv资格相同的cv资格或更高的cv资格.如果应用转换,则通过从E1复制初始化T2类型的临时值并将该临时值用作转换后的操作数,将E1更改为类型T2的prvalue.
- 否则(如果E1或E2具有非类型类型,或者它们都具有类类型但基础类不相同且两者都不是另一类的基类):如果E1可以是E1,则可以将E1转换为匹配E2在应用左值到右值(4.1),数组到指针(4.2)和函数到指针(4.3)标准转换后,隐式转换为E2将具有的类型.
使用该过程,确定是否可以转换第二操作数以匹配第三操作数,以及是否可以转换第三操作数以匹配第二操作数.如果两者都可以转换,或者一个可以转换,但转换不明确,则程序格式不正确.如果两者都不能被转换,则操作数保持不变并且如下所述执行进一步检查.如果只能进行一次转换,则将该转换应用于所选操作数,并使用转换后的操作数代替本节其余部分的原始操作数.
(再次强调我的)
所以我们有两种情况:
fst和E2是std::move(snd)std::move(snd)和E2是fst在第一种情况下,我们有E2是xvalue,所以:
如果E2是x值:如果E1可以隐式转换为"rvalue reference to T2"类型,则E1可以转换为匹配E2,受限于引用必须直接绑定.
适用; 但是E1(类型const printer)不能被隐式转换为printer&&由于它会丢失常量的事实.所以这种转换是不可能的.
在第二种情况下,我们有:
如果E2是左值:如果E1可以被隐式转换(第4条)到类型"左值引用T2",则E1可以被转换为匹配E2,受制于转换中引用必须直接绑定的约束(8.5.3) )到左值.
适用,但E1(std::move(snd)类型printer&&)可以隐式转换为const printer&,但不直接绑定到左值; 所以这个也不适用.
在这一点上,我们在:
如果E2是prvalue,或者上面的转换都不能完成,并且至少有一个操作数具有(可能是cv-qualified)类类型:
部分.
我们必须从中考虑:
如果E1和E2具有类类型,并且底层类类型相同或者一个是另一个类的基类:如果T2的类与类的类型相同,则可以转换为E1,或者基类类, T1的类别和T2的cv资格是与cv资格相同的cv资格或更高的cv资格.如果应用转换,则通过从E1复制初始化T2类型的临时值并将该临时值用作转换后的操作数,将E1更改为类型T2的prvalue.
E1和E2确实具有相同的基础类类型.并且const printer具有比cv资格更大的cv资格std::move(snd),因此情况是E1 = std::move(snd)和E2 = fst.
从中我们最终得到了:
通过从E1复制初始化T2类型的临时值并将该临时值用作转换后的操作数,将E1更改为T2类型的prvalue.
其转换到这一事实std::move(snd)改为类型的prvalue const printer通过复制初始化一个临时的类型const printer从std::move(snd).
由于std::move(snd)收益率printer&&,表达将相当于的建设const printer用printer(std::move(snd)),这将导致printer(printer&&)被选择.
| 归档时间: |
|
| 查看次数: |
394 次 |
| 最近记录: |