Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

integral_constants in ternary operator

MSVC and clang/gcc disagree on whether two different integral constants can be used in a ternary operator (and as a consequence whether they have a common_type):

#include <utility>

int main()
{
    return false
        ? std::integral_constant<int, 1>() 
        : std::integral_constant<int, 2>();
}

The above snippet compiles fine in clang and gcc, but not in MSVC. What is the correct behavior according to the standard? And if it's clang/gcc behavior then what are the conversion sequences used to deduce the common type of these two distinct types?

like image 338
Rostislav Avatar asked Feb 23 '17 16:02

Rostislav


People also ask

What is STD Integral_constant?

std::integral_constant wraps a static constant of specified type. It is the base class for the C++ type traits. The behavior of a program that adds specializations for integral_constant is undefined.

What is the use of Integral_constant?

std::integral_constant This template is designed to provide compile-time constants as types. It is used by several parts of the standard library as the base class for trait types, especially in their bool variant: see true_type and false_type. Its definition in the Standard Library has the same behavior as: C++11.


2 Answers

tldr; The code is well-formed. The conditional expression will have type int and value 2. This is an MSVC bug.


From [expr.cond]:

Otherwise, if the second and third operand have different types and either has (possibly cv-qualified) class type, or [...], an attempt is made to form an implicit conversion sequence (13.3.3.1) from each of those operands to the type of the other. [...] Attempts are made to form an implicit conversion sequence from an operand expression E1 of type T1 to a target type related to the type T2 of the operand expression E2 as follows: [...]
— If E2 is an lvalue, [...]
— If E2 is an xvalue, [...]
— If E2 is a prvalue or if neither of the conversion sequences above can be formed and at least one of the operands has (possibly cv-qualified) class type:
      — if T1 and T2 are the same class type (ignoring cv-qualification), or one is a base class of the other, and T2 is at least as cv-qualified as T1, the target type is T2,
      — otherwise, the target type is 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.

So we're trying to form an implicit conversion sequence from type std::integral_constant<int, 1> to type std::integral_constant<int, 2>. That's not viable. Nor is the implicit conversion sequence in the reverse direction viable either. These types are simply not interconvertible.

So we continue:

If no conversion sequence can be formed, the operands are left unchanged and further checking is performed as described below. [...]

If the second and third operands are glvalues of the same value category and have the same type, [...]

Otherwise, the result is a prvalue. If the second and third operands 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.

Ok, what overload resolution can we perform? From [over.match.oper]:

If either operand has a type that is a class or an enumeration, a user-defined operator function might be declared that implements this operator or a user-defined conversion can be necessary to convert the operand to a type that is appropriate for a built-in operator.

Where the builtins are specified in [over.built] as:

For every pair of promoted arithmetic types L and R, there exist candidate operator functions of the form

LR operator?:(bool, L , R );

where LR is the result of the usual arithmetic conversions between types L and R.

One of those builtins would be int operator?:(bool, int, int). Since std::integral_constant<int, V> does have an operator int(), this is a viable conversion for both arguments.

We continue in [expr.cond]:

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.

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:
— The second and third operands have the same type; the result is of that type and the result object is initialized using the selected operand.

At this point, the second and third operands do have the same type: int. So the result object is initialized as an int, and the expression is well-formed.

like image 60
Barry Avatar answered Oct 16 '22 09:10

Barry


The relevant paragraph from [expr.cond] is 6:

Otherwise, the result is a prvalue. If the second and third operands 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.

integral_constant<int> has a conversion operator to int, so this can work. Following through to 13.3.1.2, we see that by paragraph 3.2, all builtin ?: operators taking integer and floating point arguments are candidates.

Now overload resolution is performed for all these, given our three arguments. As per [over.ics.rank]/3.3, we tie-break by comparing the standard conversion sequences from int (the return type of integral_constant<int>'s conversion operator) to the builtin operators' parameter types.

However, a look at table 13 is enough; conversions to the floating point types have Conversion rank, and since int is a promoted type, converting to any integral type but int (which is an identity conversion) is an integral conversion with Conversion rank. Hence the best viable candidate is, unambiguously, operator?:(bool, int, int). That is, MSVC is wrong.

like image 42
Columbo Avatar answered Oct 16 '22 08:10

Columbo