Can someone explain this code?

Here is some code I ran across:

1
2
3
4
5
6
7
8
9
#include <utility>

template<typename T1, typename T2,
         typename RT = std::decay_t<decltype(true ? std::declval<T1>()
                                                  : std::declval<T2>())>>
RT max (T1 a, T2 b)
{
    return b < a ? a : b;
}


My question is regarding this line:
1
2
decltype(true ? std::declval<T1>()
              : std::declval<T2>())


Won't this always evaluate to the first case std::declval<T1>() because the condition is true? What is the purpose of using the ?: operator in this case? I understand that declval is only meant to be used in an unevaluated context, but I don't understand the purpose of using the ?: operator.
Last edited on
Notice that the max() uses the ?: operator.
What is the type returned by that operator?
See https://en.cppreference.com/w/cpp/language/operator_other for the rules

Put other way, the condition being true or false has no effect on the returned type, does it?
Do not duplicate type traits that already exist. This one is called... std::common_type.
I see what it is trying to do. But what I am confused about is this.
1
2
decltype(true ? std::declval<T1>()
                       : std::declval<T2>()


Within the decltype statement, the condition is given as true. When the compiler sees that the condition is true, won't it automatically assume the return type is the decayed type of T1 and then won't it give an error if the condition b < a turns out to be false and thus returns a type T2 (assuming T1 and T2 are different types)?

Logically, it seems that this function would only work if the condition b < a is true, because then the type is T1, and if it is not then it would give an error. I just don't understand why decltype(true std::declval<T1>() : std::declval<T2>() first checks the body of the function and looks at the condition for the return type ( b < a ) rather than simply seeing that there is a true inside the condition given in the decltype, thus automatically resolving to decltype(std::declval<T1>())
mbozzi wrote:
Do not duplicate type traits that already exist. This one is called... std::common_type.

This is not my code. This is from a textbook. As such, it assumes that the reader of the textbook is not familiar with all the type traits, and thus explains the usage of all of them. The section I am on has not covered std::common_type yet, thus it does not use it in the code.
Well, the advice notwithstanding, it's still a hint:
https://en.cppreference.com/w/cpp/types/common_type

The type of the expression (x ? a : b), assuming it's well formed, never depends on the value of x.
Last edited on
mbozzi wrote:
The type of the expression (x ? T{} : U{}), assuming it's well formed, never depends on the value of x.


I see. Where does it say that in cppreference? I couldn't find it in that link or in the above link given by keskiverto. Does this mean if I replaced "true" with "false" in the type, it wouldn't change anything?
Last edited on
TheToaster wrote:
Where does it say that in cppreference.
On the page @keskiverto linked.
cppreference wrote:
The type and value category of the conditional expression E1 ? E2 : E3 are determined according to the following rules: [...]


TheToaster wrote:
Does this mean if I replaced "true" with "false" in the type, it wouldn't change anything?
Yes.
Last edited on
Funny, the cppreference page about std::common_type shows possible implementation that uses ? :

Where does it say that in cppreference.

We must assume that the rules there are exhaustive.
None of them describes E1 affecting the type.
Therefore, implicitly, type never depends on E1.
TheToaster wrote:
This is from a textbook

Everything is explained in the previous example.

Quoted from:
C++ Templates, The Complete Guide, Second Edition by David Vandevoorde, Nicolai M. Josuttis, Douglas Gregor
Part I: The Basics — 1) Function Templates — 1.3) Funtion Template Parameters — 1.3.2) Deducing the Return Type
- - -
In C++11 we can benefit from the fact that the trailing return type syntax allows us to use the call parameters. That is, we can declare that the return type is derived from what operator?: yields:
1
2
3
4
5
6
7
basics/maxdecltype.hpp

template<typename T1, typename T2>
auto max (T1 a, T2 b) -> decltype(b<a?a:b)
{
    return b < a ? a : b;
}

Here, the resulting type is determined by the rules for operator ?:, which are fairly elaborate but generally produce an intuitively expected result (e.g., if a and b have different arithmetic types, a common arithmetic type is found for the result).

Note that
1
2
template<typename T1, typename T2>
auto max (T1 a, T2 b) -> decltype(b<a?a:b);

is a declaration, so that the compiler uses the rules of operator?: called for parameters a and b to find out the return type of max() at compile time. The implementation does not necessarily have to match. In fact, using true as the condition for operator?: in the declaration is enough:
1
2
template<typename T1, typename T2>
auto max (T1 a, T2 b) -> decltype(true?a:b);

- - -
(Bold is mine. I haven’t used the quote tags to improve readability)
Topic archived. No new replies allowed.