Why do these two code snippets have the same effect?
Asked Answered
G

3

10
template <typename T1, typename T2>
auto max (T1 a, T2 b) -> decltype(b<a?a:b);
template <typename T1, typename T2>
auto max (T1 a, T2 b) -> decltype(true?a:b);

I do not understand why these two code snippets can have the same effect. Plz give me some hint and a underlying explanation.

Cheers.

Gadroon answered 25/9, 2019 at 7:33 Comment(1)
Can you elaborate what is unclear to you? Do you expect the condition in the ternary operator to cause the two decltype expressions to yield different types?Concordant
K
15

Because the type returned by a ternary operator is decided according the types of the second and third arguments, not according the value of the first.

You can verify this with the following code

#include <type_traits>

int main ()
 {
   auto x = true ? 1 : 2l;

   static_assert( std::is_same<decltype(x), long>::value, "!" );
 }

Isn't important that true ? 1 : 2l return ever 1; the ternary operator return a common type between 1 (int) and 2l (long). That is long.

In other words: there isn't (at the moment) a constexpr ternary operator.

Kraut answered 25/9, 2019 at 7:39 Comment(10)
Yes. But i think i just dont understand the actual meaning of true ? a : b, true is a chosen condition, it would not change , right ?Gadroon
And that's why decltype(true ? a : b) will always return the type of a, right?Gadroon
@Gadroon - No. true ? a : b will always return the value of a; but the type returned depends also from b. The compiler can't (according the C++ language rules) make the following reasoning: "true is always true, so I always return a, so I return the type of a". The compiler must always return (isn't important if the value of the condition is known at compile time or not) a common type between a and b. It's the same problem that you have with if and if constexpr. There isn't a constexpr ternary operator.Kraut
Yes, I know it is a type. So the return type is more based on my implementation, right? if I want the larger value, then decltype(true ? a : b) will always be the type of a, but the content of a will change according to my function implementation, right?Gadroon
@Gadroon No, you keep saying that, it is exactly wrong. The type of an expression never depends on it's valueThole
@Gadroon - "decltype(true ? a : b) will always be the type of a" - Wrong. The type of true ? a : b will always be a type that depends from the type of a and the type of b. The fact that the condition is true, false or a value that is known only run-time is absolutely insignificant.Kraut
Ok, I am so confused. Plz just tell me how decltype(true ? a : b) can give me the right return type. If my function is to get the larger value of a and b.Gadroon
@Gadroon You can't make the type depend on the values of a and b, only on their types (and it depends on both of them, regardless).Clifford
So, decltype() can actually hold any expression, right? I only need to include all of the variables in this expression. The final return value and type depends on my function implementation, right? For example, if I put decltype(a+b), it will still work, am i there?Gadroon
@Gadroon Yes, but not all types have +, nor does it always yield the desirable type.Affirm
C
8

The type of a conditional expression does not depend on whether the condition is true or not.

decltype(b<a?a:b) is the type of the expression b<a?a:b, which is always the same.
It is not either decltype(a) or decltype(b) depending on the value of b<a.

Note that the expression you give to decltype is never evaluated - only its type is determined, and it is determined at compile time.

Somewhat informally:

  • if a can be converted to the type of b, the expression has the same type as b
  • if b can be converted to the type of a, the expression has the same type as a
  • otherwise, the expression is ill-typed.

(There's also a trtuckload of nitty-gritty details about standard conversions and yada-yada involved, but this is the gist of it.)

For instance, this will not compile becuse there are no valid conversions that could give notgood a type:

int main()
{
     decltype(true ? "hello" : 123.4) notgood;
}

while this will compile, and run, and be well-defined, because the invalid dereference is never evaluated:

int main()
{
    decltype(*(int*)0 + 1)` x = 0;
    return x;
}
Clifford answered 25/9, 2019 at 7:44 Comment(2)
I thought if I wrote true ? a : b, then decltype(true ? a : b) would always be the type of a, but it actually not, this is the point that I don't understand.Gadroon
It's very difficult to explain any further than the details provided in these answers. The most important point is that the type of a conditional expression does not depend on any of the values involved.Clifford
I
3

The rules for determining the type of a conditional expression are described here.

As the others have already said, the key is to realize that the expression

E1 ? E2 : E3

taken as a whole is an expression, and an expression has a single type (and value category) determined at compile time. It can't change type depending on which path is taken, because in general that isn't known until runtime.

So, the rules are pretty extensive. Skipping the void and bit-field special cases, it works something like this:

  1. If either E2 or E3 has type void ... assume they don't.
  2. Otherwise, if E2 or E3 are glvalue bit-fields ... assume they aren't.
  3. Otherwise, if E2 and E3 have different types, at least one of which is a (possibly cv-qualified) class type ...

    OK, this one might be true. We don't yet know the types of E1 and E2, but it's certainly plausible.

    If this case applies, there's a whole list of steps it must follow, and if it succeeds then it figured out either how to implicitly convert E1 to E2, or E2 to E1. Whichever, we pick up at the next step with two subexpressions of the same type.

  4. If E2 and E3 are glvalues of the same type and the same value category, then the result has the same type and value category

    That is, if our original T1 and T2 are the same, then the type of the expression is just that. This is the simplest case.

    If they're different types but the compiler figured out an implicit conversion in step 3 above, we're looking at either (T1,T1) or (T2,T2) and the same applies.

  5. Otherwise, the result is a prvalue [roughly - anonymous temporary]. If E2 and E3 do not have the same type, and either has (possibly cv-qualified) class type, overload resolution is performed using the built-in candidates below to attempt to convert the operands to built-in types ... the converted operands are used in place of the original operands for step 6

    Maybe they're classes with conversion operators like operator bool - then we haven't found another answer, so we'll do the conversion to bool and keep going.

  6. The lvalue-to-rvalue, array-to-pointer, and function-to-pointer conversions are applied to the second and third operands

    These are a bunch of standard implicit conversions just to make both sides as similar as possible.

    Then,

    1. If both E2 and E3 now have the same type, the result is a prvalue of that type

      We managed to massage both sides to have the same type, hooray!

    2. If both E2 and E3 have arithmetic or enumeration type: the usual arithmetic conversions are applied to bring them to common type, and that type is the result

      The usual arithmetic conversions are what allow you to add, say, and int and a double and get some result. This will work the same way.

    3. etc. etc.

Intrauterine answered 25/9, 2019 at 8:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.