How std::conditional works
Asked Answered
N

3

21

We have this little metaprogramming marvel called std::conditional described here. In the same reference it says that a possible implementation is

template<bool B, class T, class F>
struct conditional { typedef T type; };

template<class T, class F>
struct conditional<false, T, F> { typedef F type; };

So if in code I do something like

typename std::conditional<true,int,double>::type a;

the compiler will follow the first definition and if I do something like

typename std::conditional<false,int,double>::type b

the compiler will take the second. Why does that work ? What compilation rule is in place here ?

Nonparticipating answered 14/6, 2017 at 17:18 Comment(1)
The rule in place is partial template specialization.Overlooker
B
17

In short it has to do with template specialization rules. These are like function overloads but for types.

Here compiler prefers a more specialized type to the more general type.

template<bool B, class T, class F>
struct conditional { typedef T type; };

template<class T, class F>
struct conditional<false, T, F> { typedef F type; };

So if a template instantiation conditional<false, ...> is seen by compiler it finds:

  • the general template template<bool B, class T, class F> struct conditional
  • all its specializations: template<class T, class F> struct conditional<false, T, F>

At that point it tries to match as many specialized arguments as possible and ends up selecting the specialization with false.

For example introducing another version like:

template<class F>
struct conditional<false, int, F> { typedef int type; };

and instantiating a template type like conditional<false, int, double> will prefer specialization

template<class F> struct conditional<false, int, F>

to

template<class T, class F> struct conditional<false, T, F> which is more general compared to the version with 2 specialized paramaters.

Some tricks at this point:

It is even OK to just declare the most generic case (i.e. generic form of the template is just declared but not defined) and only have specializations for cases you really intend to have. All non-specialized cases will result in compile errors and asking the user to specialize the case for a particular type.

Burberry answered 14/6, 2017 at 17:59 Comment(1)
Oh, nice. Thank you very much. This meta-programming tricks are so smart and at the same time looks like hacking. I wonder if this type_traits one day will be first citizens in C++ and be implemented directly by the compiler in a kind of simpler template conditional language. Although it might be a real challenge for the compiler developers.Nonparticipating
F
4

Why does that work ? What compilation rule is in place here ?

I'm not an expert but I'll try to explain from the pratical point of view.

Hoping to use the rights terms...

With

template <bool B, class T, class F>
struct conditional { typedef T type; };

(but, with C++11, I prefer

template <bool B, typename T, typename>
struct conditional { using type = T; };

) you declare the template

template <bool, typename, typename>
struct conditional;

and define the generic (not specialized) version.

With

template< class T, class F>
struct conditional<false, T, F> { typedef F type; };

(or, in C++11,

template <typename T, typename F>
struct conditional<false, T, F> { using type = F; };

) you define a partial specialization of conditional

When the template arguments of a class (or struct) match two or more definitions, the compliler choose the more specialized one; so, for

typename std::conditional<false,int,double>::type

both definitions of the class match so the compiler choose the specialized one (the specialized with false) anche type is double.

For

typename std::conditional<true,int,double>::type a;

only the generic version match, so type is int.

Furring answered 14/6, 2017 at 17:52 Comment(1)
Yeah, I didn't realize that the second declaration were actually seen as an specialization. The more specialized, the more precedence it has. Amazing stuff. ThanksNonparticipating
M
2

In most of the template meta programming main rule at play here is SFINAE(Substitution failure is not an error). According to this rule if compiler not find the type then instead of throwing an error it moves forward and check if there is any future statement that can provide type information.

Manchester answered 4/4, 2020 at 8:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.