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.

Jing Ma
  • 183
  • 2
  • 12
Edee
  • 1,746
  • 2
  • 6
  • 14
  • 4
    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? – walnut Sep 25 '19 at 07:36

3 Answers3

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.

max66
  • 65,235
  • 10
  • 71
  • 111
  • 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 ? – Edee Sep 25 '19 at 07:54
  • And that's why decltype(true ? a : b) will always return the type of a, right? – Edee Sep 25 '19 at 08:01
  • 1
    @Edee - 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. – max66 Sep 25 '19 at 08:43
  • 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? – Edee Sep 25 '19 at 08:44
  • @Edee No, you keep saying that, it is *exactly* wrong. The *type* of an expression **never** depends on it's *value* – Caleth Sep 25 '19 at 08:46
  • @Edee - "`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. – max66 Sep 25 '19 at 08:49
  • 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. – Edee Sep 25 '19 at 08:54
  • @Edee 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). – molbdnilo Sep 25 '19 at 08:57
  • 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? – Edee Sep 25 '19 at 09:01
  • @Edee Yes, but not all types have `+`, nor does it always yield the desirable type. – L. F. Sep 25 '19 at 09:12
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;
}
molbdnilo
  • 64,751
  • 3
  • 43
  • 82
  • 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. – Edee Sep 25 '19 at 08:35
  • 2
    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. – molbdnilo Sep 25 '19 at 09:02
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.

Useless
  • 64,155
  • 6
  • 88
  • 132