Peregring-lk Peregring-lk - 3 years ago 168
C++ Question

Ternary operator with same underlying class type

Should this program output 0 or 1? In my reading and understanding of the cited paragraphs from the C++14 standard, it should print 1, but both GCC and clang prints 0 (because the deduced type is

A const
instead of
A const&
):

#include <iostream>

struct A {};

int main()
{
A a;
A const& ra = std::move(a); // #1

std::cout << std::is_same<decltype(true ? ra : std::move(a)),
A const&>::value; // Prints 0
}


In this case,
ra
is a
A const
lvalue, and
std::move(a)
is a
A
xvalue, both of class-types. According to the standard about the ternary operator (emphasis mine), the result should be an
lvalue
of type
A const
, and thus the
decltype
result must be
A const&
:


[expr.cond]/3 Otherwise, 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.

[...]


In this case, E2 is
ra
, which is a lvalue, and the other can be implicitely converted to "lvalue reference to T2", as shown in line
// #1
. "lvalue reference to T2" is translated as
A const&
, so,
std::move(a)
binds directly to a lvalue of type
A const
, and after the conversion, both operands have same type and value category, and thus:


[expr.cond]/3 If the second and third operands are glvalues of the same value category and have the same type, the result is of that type and value category [...].


So, the operator result should be an lvalue and the
decltype
result should be a reference, and thus the program should print 1.

Answer Source

The question is awkwardly worded. You should instead ask what the type and value category of the expression true ? ra : std::move(a) should be. The answer to that question is a prvalue of type A const. This subsequently means the program should print 0, as I think every compiler correctly does.


The rules for ?: are fairly complex. In this case, we have two expressions of class type that we try to see if we can convert to each other, based on a limited subset of rules.

Attempting the conversion rastd::move(a) fails. We first try with a target type is A&& which can't bind directly to ra. We then try the backup plan in (3.3.1) since the two expressions have the same underlying class type, but our target expression is not at least as cv-qualified as the source expression, so this also fails.

Attempting the conversion std::move(a)ra fails (3.1) because we need to bind directly to an lvalue (we can bind an rvalue to a const lvalue reference, but here we are required to bind an lvalue). But, the (3.3.1) backup succeeds because now the target type is at least as cv-qualified as the source.

Hence, we apply the conversion and we continue as if the second operand were an lvalue of type A const but the third operand is now a prvalue of type A const (instead of an xvalue of type A).

(4) fails, because they're not of the same value category.

Hence, the result is a prvalue. And since they have the same type, the result is of that type: A const.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download