移出三元运算符的一侧
Moving out of one side of a ternary operator
我正在编写类似于以下代码:
std::string foo(bool b, const std::string& fst, std::string&& snd) {
return b ? fst : std::move(snd);
}
and clang and copy snd
out while gcc
move it out.
我试图最小化这个例子,我想出了:
#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);
}
gcc 5.2 输出
move
clang 3.6 输出
const rvalue ref
标准是否同时允许 gcc 和 clang 行为?
以下随机观察:
gcc和clang都将三元的类型统一为:
const printer
类型std::move(x)
好的,让我们从弄清楚std::move(snd)
的类型开始。 std::move(x)
的实现被定义为大约 static_cast<T&&>(x)
,根据 §20.2.4:
template <class T> constexpr remove_reference_t<T>&& move(T&& t) noexcept;
Returns: static_cast<remove_reference_t<T>&&>(t)
.
根据 §5.2.9/1:
The result of the expression static_cast<T>(v)
is the result of converting the expression v
to type T
. If T
is an lvalue reference type or an rvalue reference to function type, the result is an lvalue; if T
is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue. The static_cast
operator shall not cast away constness (5.2.11).
(强调我的)
好的,所以 std::move(snd)
的返回值是一个 printer&&
类型的 xvalue。 fst
的类型是 const printer
.
类型的左值
常见类型如何计算
现在,标准描述了计算三元条件运算符结果表达式类型的过程:
if the second and third operand have different types and either has (possibly cv-qualified) class type, or if both are glvalues of the same value category and the same type except for cv-qualification, an attempt is made to convert each of those operands to the type of the other. The process for determining whether an operand expression E1 of type T1 can be converted to match an operand expression E2 of type T2 is defined as follows:
- If E2 is an lvalue: E1 can be converted to match E2 if E1 can be implicitly converted (Clause 4) to the type “lvalue reference to T2”, subject to the constraint that in the conversion the reference must bind directly (8.5.3) to an lvalue.
- If E2 is an xvalue: E1 can be converted to match E2 if E1 can be implicitly converted to the type “rvalue reference to T2”, subject to the constraint that the reference must bind directly.
If E2 is a prvalue or if neither of the conversions above can be done and at least one of the operands has (possibly cv-qualified) class type:
- if E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or a base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1. If the conversion is applied, E1 is changed to a prvalue of type T2 by copy-initializing a temporary of type T2 from E1 and using that temporary as the converted operand.
- Otherwise (if E1 or E2 has a non-class type, or if they both have class types but the underlying classes are not the same and neither is a base class of the other): E1 can be converted to match E2 if E1 can be implicitly converted to the type that E2 would have after applying the lvalue-to-
rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions.
Using this process, it is determined whether the second operand can be converted to match the third operand, and whether the third operand can be converted to match the second operand. If both can be converted, or one can be converted but the conversion is ambiguous, the program is ill-formed. If neither can be converted, the operands are left unchanged and further checking is performed as described below. If exactly one conversion is possible, that conversion is applied to the chosen operand and the converted operand is used in place of the original operand for the remainder of this section.
(再次强调我的)
在这种情况下
所以我们有两种情况:
- E1 是
fst
,E2 是 std::move(snd)
- E1 是
std::move(snd)
,E2 是 fst
在第一种情况下我们有 E2 是一个 xvalue,所以:
If E2 is an xvalue: E1 can be converted to match E2 if E1 can be implicitly converted to the type “rvalue reference to T2”, subject to the constraint that the reference must bind directly.
适用;但是 E1(const printer
类型)不能隐式转换为 printer&&
,因为它会失去常量性。所以这个转换是不可能的。
在第二种情况下,我们有:
If E2 is an lvalue: E1 can be converted to match E2 if E1 can be implicitly converted (Clause 4) to the type “lvalue reference to T2”, subject to the constraint that in the conversion the reference must bind directly (8.5.3) to an lvalue.
适用,但 E1(printer&&
类型的 std::move(snd)
)可以隐式转换为 const printer&
,但不直接绑定到左值;所以这个也不适用。
此时我们处于:
If E2 is a prvalue or if neither of the conversions above can be done and at least one of the operands has (possibly cv-qualified) class type:
节。
从中我们不得不考虑:
if E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or a base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1. If the conversion is applied, E1 is changed to a prvalue of type T2 by copy-initializing a temporary of type T2 from E1 and using that temporary as the converted operand.
E1 和 E2 确实具有相同的基础 class 类型。并且 const printer
的 cv 限定大于 std::move(snd)
的 cv 限定,因此情况是 E1 = std::move(snd)
和 E2 = fst
.
从中我们终于得到了:
E1 is changed to a prvalue of type T2 by copy-initializing a temporary of type T2 from E1 and using that temporary as the converted operand.
这转化为 std::move(snd)
通过从 std::move(snd)
.
由于 std::move(snd)
产生 printer&&
,表达式将等同于 const printer
和 printer(std::move(snd))
的构造,结果应该是 printer(printer&&)
被选中。
我正在编写类似于以下代码:
std::string foo(bool b, const std::string& fst, std::string&& snd) {
return b ? fst : std::move(snd);
}
and clang and copy snd
out while gcc
move it out.
我试图最小化这个例子,我想出了:
#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);
}
gcc 5.2 输出
move
clang 3.6 输出
const rvalue ref
标准是否同时允许 gcc 和 clang 行为?
以下随机观察:
gcc和clang都将三元的类型统一为:
const printer
类型std::move(x)
好的,让我们从弄清楚std::move(snd)
的类型开始。 std::move(x)
的实现被定义为大约 static_cast<T&&>(x)
,根据 §20.2.4:
template <class T> constexpr remove_reference_t<T>&& move(T&& t) noexcept;
Returns:
static_cast<remove_reference_t<T>&&>(t)
.
根据 §5.2.9/1:
The result of the expression
static_cast<T>(v)
is the result of converting the expressionv
to typeT
. IfT
is an lvalue reference type or an rvalue reference to function type, the result is an lvalue; ifT
is an rvalue reference to object type, the result is an xvalue; otherwise, the result is a prvalue. Thestatic_cast
operator shall not cast away constness (5.2.11).
(强调我的)
好的,所以 std::move(snd)
的返回值是一个 printer&&
类型的 xvalue。 fst
的类型是 const printer
.
常见类型如何计算
现在,标准描述了计算三元条件运算符结果表达式类型的过程:
if the second and third operand have different types and either has (possibly cv-qualified) class type, or if both are glvalues of the same value category and the same type except for cv-qualification, an attempt is made to convert each of those operands to the type of the other. The process for determining whether an operand expression E1 of type T1 can be converted to match an operand expression E2 of type T2 is defined as follows:
- If E2 is an lvalue: E1 can be converted to match E2 if E1 can be implicitly converted (Clause 4) to the type “lvalue reference to T2”, subject to the constraint that in the conversion the reference must bind directly (8.5.3) to an lvalue.
- If E2 is an xvalue: E1 can be converted to match E2 if E1 can be implicitly converted to the type “rvalue reference to T2”, subject to the constraint that the reference must bind directly.
If E2 is a prvalue or if neither of the conversions above can be done and at least one of the operands has (possibly cv-qualified) class type:
- if E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or a base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1. If the conversion is applied, E1 is changed to a prvalue of type T2 by copy-initializing a temporary of type T2 from E1 and using that temporary as the converted operand.
- Otherwise (if E1 or E2 has a non-class type, or if they both have class types but the underlying classes are not the same and neither is a base class of the other): E1 can be converted to match E2 if E1 can be implicitly converted to the type that E2 would have after applying the lvalue-to- rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions.
Using this process, it is determined whether the second operand can be converted to match the third operand, and whether the third operand can be converted to match the second operand. If both can be converted, or one can be converted but the conversion is ambiguous, the program is ill-formed. If neither can be converted, the operands are left unchanged and further checking is performed as described below. If exactly one conversion is possible, that conversion is applied to the chosen operand and the converted operand is used in place of the original operand for the remainder of this section.
(再次强调我的)
在这种情况下
所以我们有两种情况:
- E1 是
fst
,E2 是std::move(snd)
- E1 是
std::move(snd)
,E2 是fst
在第一种情况下我们有 E2 是一个 xvalue,所以:
If E2 is an xvalue: E1 can be converted to match E2 if E1 can be implicitly converted to the type “rvalue reference to T2”, subject to the constraint that the reference must bind directly.
适用;但是 E1(const printer
类型)不能隐式转换为 printer&&
,因为它会失去常量性。所以这个转换是不可能的。
在第二种情况下,我们有:
If E2 is an lvalue: E1 can be converted to match E2 if E1 can be implicitly converted (Clause 4) to the type “lvalue reference to T2”, subject to the constraint that in the conversion the reference must bind directly (8.5.3) to an lvalue.
适用,但 E1(printer&&
类型的 std::move(snd)
)可以隐式转换为 const printer&
,但不直接绑定到左值;所以这个也不适用。
此时我们处于:
If E2 is a prvalue or if neither of the conversions above can be done and at least one of the operands has (possibly cv-qualified) class type:
节。
从中我们不得不考虑:
if E1 and E2 have class type, and the underlying class types are the same or one is a base class of the other: E1 can be converted to match E2 if the class of T2 is the same type as, or a base class of, the class of T1, and the cv-qualification of T2 is the same cv-qualification as, or a greater cv-qualification than, the cv-qualification of T1. If the conversion is applied, E1 is changed to a prvalue of type T2 by copy-initializing a temporary of type T2 from E1 and using that temporary as the converted operand.
E1 和 E2 确实具有相同的基础 class 类型。并且 const printer
的 cv 限定大于 std::move(snd)
的 cv 限定,因此情况是 E1 = std::move(snd)
和 E2 = fst
.
从中我们终于得到了:
E1 is changed to a prvalue of type T2 by copy-initializing a temporary of type T2 from E1 and using that temporary as the converted operand.
这转化为 std::move(snd)
通过从 std::move(snd)
.
由于 std::move(snd)
产生 printer&&
,表达式将等同于 const printer
和 printer(std::move(snd))
的构造,结果应该是 printer(printer&&)
被选中。