In template metaprogramming with C++11 and onwards, it is considered standard to write and call metafunctions with a single list of parameters, much like conventional functions. Sometimes, this is far from intuitive, and while alternatives can lead to a little more verbosity, they can also lead to greater intuitive readability.

Conditional Statements

Consider a conditional metafunction. Of all conditional patterns, the most recognizable are the if/else pattern and the ternary pattern; the latter of which is the easier of the two to implement with template metaprogramming by using a single template specialization. This is the pattern that the standard library chooses to use with std::conditional.

template<bool, class, class F> struct conditional{ using type = F; }; template<class T, class F> struct conditional<true, T, F>{ using type = T; }; template<bool B, class T, class F> using conditional_t = typename conditional<B, T, F>::type; //calling syntax: //conditional_t<B, T, F> //use case: template<class T> using is_integral_t = conditional_t< std::is_integral<T>::value , std::true_type , std::false_type >; //invocation: is_integral_t<int> //std::true_type is_integral_t<double> //std::false_type

While this works well and is the most obvious implementation, the ternary pattern isn’t always the most intuitive to use. So let’s implement a more natural equivalent that exhibits the if/else pattern as opposed to the ternary pattern. To do so, of course, will require that we can trail template parameter clauses: one for if, another for else.

template<bool, class> struct If{ template<class F> using Else = F; }; template<class T> struct If<true, T>{ template<class> using Else = T; }; //calling syntax: //If<B, T>::Else<F> //typename If<B, T>::template Else<F> //use case: template<class T> using is_integral_t = typename If<std::is_integral<T>::value, std::true_type> ::template Else<std::false_type>; //invocation: is_integral_t<int> //std::true_type is_integral_t<double> //std::false_type

By placing an Else metafunction within the If metafunction, we are effectively currying the template parameters, so that the calling syntax can be trailed. Notice the necessary ::template Else in the case of alias declarations; this is far from ideal, but it’s often necessary short of preprocessor macros.

As it turns out, this basic pattern is very versatile. For instance, consider that not every if statement need have an else clause; perhaps we would then want to mimic a static assert behavior if the condition inside of If is not met. As such, we could add the following.

template<bool, class> struct If{ template<class F> using Else = F; }; template<class T> struct If<true, T>{ template<class> using Else = T; using ElseError = T; }; //calling syntax: //If<B, T>::Else<F> //typename If<B, T>::template Else<F> //If<B, T>::ElseError //use case: template<class T> using is_integral_t = typename If<std::is_integral<T>::value, std::true_type> ::ElseError; //invocation: is_integral_t<int> //std::true_type is_integral_t<double> //compile error!

Higher Order Metafunctions

Now consider a fold metafunction. In a standard implementation, the list that is being folded over is in the same argument list as the accumulator and the binary function. However, this can lead to unnatural calling syntax; because the binary function and the accumulator define the properties of the fold and the list is the subject of the fold, it makes sense to separate these parameters.

So, as a motivating example, we will implement the right fold as a metafunction which folds a binary metafunction onto a list of types, using an accumulator type. Then, we will apply this to write a metafunction that extracts the largest type in a list of types by folding a size comparison function onto a list of types.

template<class...> struct TypeList{}; //right fold using standard metaprogramming syntax template<class, template<class, class> class, class> struct _FoldRight; template<template<class, class> class F, class Acc> struct _FoldRight<TypeList<>, F, Acc>{ using type = Acc; }; template< class Head , class... Tail , template<class, class> class F , class Acc > struct _FoldRight<TypeList<Head, Tail...>, F, Acc>{ using type = F< Head , typename _FoldRight< TypeList<Tail...>, F, Acc >::type >; }; //calling syntax: //_FoldRight<List, F, Acc> //right fold using template currying template<template<class, class> class F, class Acc> struct FoldRight{ template<class List> using Onto = typename _FoldRight<List, F, Acc>::type; }; //calling syntax: //FoldRight<F, Acc>::Onto<List>; //typename FoldRight<F, Acc>::template Onto<List>; //size comparison metafunction template<class T, class U> using SizeMax = typename If<sizeof(T) < sizeof(U), U> ::template Else<T>; //use case: template<class List> using LargestType = typename FoldRight<SizeMax, char> ::template Onto<List>; //invocation: LargestType<TypeList<int, short, double, char>>; //double