Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How std::conditional works

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 ?

like image 484
alcoforado Avatar asked Jun 14 '17 17:06

alcoforado


2 Answers

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 coditional<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.

like image 96
ovanes Avatar answered Nov 16 '22 10:11

ovanes


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.

like image 4
max66 Avatar answered Nov 16 '22 08:11

max66