5
votes

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

1
this potentially affects the behavior of std::common_type, no? or at least, the default implementation: en.cppreference.com/w/cpp/types/common_typeChris Beck
@ChrisBeck In this particular case no because the template parameters are first decayed.Peregring-lk
@Peregring-lk You should never modify a question in a way that can invalidate other people comments (or answers).Phil1970
It's ok, he should just mention what compilers do what in the question after he changes it imo. Comments are lightweight. Invalidating answers is bad though, should just ask a new questionChris Beck
There is no type deduction in this code. By "deduced type" I guess you actually refer to the common type of the second and third operand of the conditional operator.M.M

1 Answers

6
votes

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.