Alexandre C.
Alexandre C.

Reputation: 56956

What is the type of `a ? b : c`?

Let's say we have

template <typename T>
struct Foo
{};

and

struct Bar
{
    template <typename T>
    operator Foo<T>() const { return Foo<T>(); }
};

and

template <typename T>
Foo<T> Baz(T const&) { return Foo<T>(); }

Then, true ? Bar() : Baz(some_expr_of_type_double) has type Foo<double> because Bar is convertible to Foo<double>. This trick is used to query the type of some_expr_of_type_double without evaluating it.

What are the rules for determining the type of a ? b : c ? I'd appreciate the relevant part of the standard (I don't have a copy). Is there more than "typeof(b) must be convertible to typeof(c) or vice versa, unambiguously" ?

Upvotes: 6

Views: 517

Answers (4)

John Dibling
John Dibling

Reputation: 101456

Here is the relevant specification:

5.16 Conditional operator [expr.cond]

  1. Conditional expressions group right-to-left. The first expression is implicitly converted to bool (clause 4). It is evaluated and if it is true, the result of the conditional expression is the value of the second expression, otherwise that of the third expression. All side effects of the first expression except for destruction of temporaries (12.2) happen before the second or third expression is evaluated. Only one of the second and third expressions is evaluated.

  2. If either the second or the third operand has type (possibly cv-qualified) void, then the lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed on the second and third operands, and one of the following shall hold:

    — The second or the third operand (but not both) is a throw-expression (15.1); the result is of the type of the other and is an rvalue.

    — Both the second and the third operands have type void; the result is of type void and is an rvalue.

    [Note: this includes the case where both operands are throw-expressions. ]

  3. Otherwise, if the second and third operand have different types, and either has (possibly cv-qualified) class type, 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:

    3.a: If E2 is an lvalue: E1 can be converted to match E2 if E1 can be implicitly converted (clause 4) to the type “reference to T2”, subject to the constraint that in the conversion the reference must bind directly (8.5.3) to E1.

    3.b: If E2 is an rvalue, or if the conversion above cannot be done:

    3.b.1 : 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 cvqualification than, the cv-qualification of T1. If the conversion is applied, E1 is changed to an rvalue of type T2 that still refers to the original source class object (or the appropriate subobject thereof). (Note: that is, no copy is made. )

    3.b.2 : Otherwise (i.e., if E1 or E2 has a nonclass type, or if they both have class types but the underlying classes are not either the same or one a base class of the other): E1 can be converted to match E2 if E1 can be implicitly converted to the type that expression E2 would have if E2 were converted to an rvalue (or the type it has, if E2 is an rvalue).

    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.

  4. If the second and third operands are lvalues and have the same type, the result is of that type and is an lvalue.

  5. Otherwise, the result is an rvalue. If the second and third operand do not have the same type, and either has (possibly cv-qualified) class type, overload resolution is used to determine the conversions (if any) to be applied to the operands (13.3.1.2, 13.6). If the overload resolution fails, the program is ill-formed. Otherwise, the conversions thus determined are applied, and the converted operands are used in place of the original operands for the remainder of this section.

  6. Lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-pointer (4.3) standard conversions are performed on the second and third operands. After those conversions, one of the following shall hold:

    6.a: The second and third operands have the same type; the result is of that type.

    6.b: The second and third operands have arithmetic or enumeration type; the usual arithmetic conversions are performed to bring them to a common type, and the result is of that type.

    6.c: second and third operands have pointer type, or one has pointer type and the other is a null pointer constant; pointer conversions (4.10) and qualification conversions (4.4) are performed to bring them to their composite pointer type (5.9). The result is of the composite pointer type.

    6.d: The second and third operands have pointer to member type, or one has pointer to member type and the other is a null pointer constant; pointer to member conversions (4.11) and qualification conversions (4.4) are performed to bring them to a common type, whose cv-qualification shall match the cvqualification of either the second or the third operand. The result is of the common type.

Upvotes: 4

rtpg
rtpg

Reputation: 2439

The standard(a current draft of which you can find here) does a thorough, if not necessarily clear, job of explaining the exact typing procedure. What I got from it was that for an expression (BOOL?Y:Z):

it firsts check for implicit or explicit conversion type from Y to Z. Then it checks for a similar base class, or if Y can be class-casted to Z.

Granted, the standard is pretty indigestible and I could be extremely off here, but from what I've read, the part that stands out is that we're always attempting Y->Z conversions. This would leave us to believe that the result is also of type Z. An interesting side effect of this is if we were to place X=(BOOL?Y:Z) and there existed Y->X and Z->X conversions, but no Y->Z conversion, then the program would be incorrect.

Upvotes: 0

fredoverflow
fredoverflow

Reputation: 263118

Do you have a C++0x compiler? In that case decltype(a ? b : c) maybe? Not sure if this handles references correctly, though. Anyone?

I'd appreciate the relevant part of the standard (I don't have a copy)

You can get the current draft for free from the WG21 website.

Upvotes: 0

Edward Strange
Edward Strange

Reputation: 40859

It is more than typeof(b) must be convertible to typeof(c). It's a complex set of rules laid out in 5.16:2-6; about a page in length. There's an order of attempts and a bunch of stuff about lvalue vs. rvalue.

Upvotes: 3

Related Questions