NiHu  2.0
Metaprogramming concepts in NiHu

Introduction

This document briefly summarises the metaprogramming concepts used in NiHu. Understanding the code snippets contained in this document requires your being familiar with the usage of variadic templates, introduced in the C++11 standard. If you are not familiar with variadic templates it is advised to read C++11 techniques in NiHu first.

Nomenclature

In the following some metaprogramming elements are explained. Most of the nomenclature explained herein is very similar to the definitions of boost, so if you are familiar with boost, you might as well skip this section.

Metafunctions

Metafunctions are program elements (classes in C++) that can return computed types. Whereas a function can receive its arguments as values and computes a return value, a metafunction can receive its arguments as template parameters and computes a return type. One great difference is that while functions compute their return value in runtime, metafunctions must be evaluated in compile time. Metafunctions are implemented in NiHu usings simple structures with nested type definitions, called type.

struct AMetaFunction
{
typedef int type;
};

Argument selector

The argument selector is a special templated metafunction that can return its nth template parameter. This might seem a trivial and unuseful task, however, you should remind yourself that once a template is instantiated there is no way to obtain the actual value of the template parameter from outside the given template. The implementation of the argument selector looks like:

template <unsigned N, class T, class ...Args>
struct select_argument : select_argument<N-1, Args...> {};
template <class T, class ...Args>
struct select_argument<1U, T, Args...>
{
typedef T type;
};

In the above code the key role of recursion is clearly followable. The general case of the select_argument template class is defined by means of a simple recursion rule, whereas the recursion tail is a partial specialisation of the general case.

Metafunction classes

Metafunction classes (a.k.a. metafunctors) are classes with a templated inner class that is a metafunction. Just as function classes (a.k.a. functors) have the evaluation operator function that can return a value, metafunction classes have a nested metafunction, that can return a type, as described above. In NiHu, the nested metafunction is always called apply.

struct AMetaFunctionClass
{
template <typename T>
struct apply
{
typedef T type;
};
};

Placeholders

Placeholders are special metafunction classes that contain an argument selector as the nested class. Placeholders do not have any other specialities what would make them differ from other metafunctors, yet, implemenetation of template metaprogramming control sequences and algorithms involve the extensive usage of placeholders, which makes them worth explaining separately.

template <unsigned N>
struct arg
{
template <class...Args>
struct apply : select_argument<N, Args...> {};
};

For the sake of convenience placeholders are referred to through simplified type definitions.

typedef arg<1> _1;
typedef arg<2> _2;
...

Placeholder expressions

Placeholder expression is a term referring either to

  1. a placeholder
  2. a template specialisation with at least one argument that is a placeholder expression

Deciding whether an expression is a placeholder expression or not, is determined recursively. The snippet below demonstrates how the decision is implemented using variadic templates. (Note: the metafunction called containsPlaceholderExpression, which is not demonstrated here, can tell if an argument list contains a placeholder expression.)

template <class C>
struct isPlaceholderExpression : std::false_type {};
template <unsigned N>
struct isPlaceholderExpression<arg<N> > : std::true_type {};
template <template <class...Args> class MF, class...Args>
struct isPlaceholderExpression< MF<Args...> > :
containsPlaceholderExpression<Args...> {};

Noticing that the variadic template denoted as MF<Args...> refers to a metafunction with the argument Args, it is straightforward that the above code implements the decision based on the criteria listed above.

Lambda expressions

Is a term referring either to

  1. a metafunction class
  2. or (as a special case) a placeholder expression.

The implementation details of lambda expressions are not discussed herein, however, the corresponding code can be found in lambda.hpp.

The reason for using lambda and placeholder expressions is to achieve lazy evaluation of types. By exploiting the power of lambda expressions code like the example below can be written:

template <class T>
struct MyMF {
struct type {
// Assuming T defines value as an integer expression
void operator() (void) {cout << T::value << endl;}
};
};
typedef vector<int_<1>, int_<2>, int_<3> > vec; // A type vector
typedef MyMF<_1> lazy_exp; // Type expression with lazy evaluation
call_each<vec, lazy_exp >(); // Evaluation performed here

Looking at the last line of the code, it can be immediately seen that the types listed in the vector type vec are automatically substituted as template parameters of the MyMF metafunction. (Note: the placeholder _1 in the penultimate line implies that the first template parameter of the resulting lambda expression will be substituted as the template parameter of class MyMF later.) Compilation of MyMF is performed only when the types in vec are already known and the call_each function is compiled.

Lambda metafunctions

Are special metafunctions, which take a lambda expression as a template parameter. The purpose of using lambda metafunctions is to turn placeholder expressions into metafunction classes. Lambda metafunctions contain a nested metafunction class. (This means that the lambda metafunction has a type definition, called type with a nested class called apply. The latter apply class also defines a type called type.)

The wrapping mechanism works as follows:

  1. If the wrapped class is a placeholder expression the lambda metafunction uses an internal metafunction class with the appropriate implementation of apply for the substitution of argument types into placeholders.
  2. Otherwise, the resulting type is the wrapped type itself.

The implementation of this functionality is simple using type definition forwarding.

template <class Exp>
struct lambda : if_<
typename isPlaceholderExpression<Exp>::type,
typename internal::lambda_plExp<Exp>::type,
Exp
> {};

Taking the example from the previous section evaluating the lazy type for one specific template parameter can be done like the following:

typedef MyMF<_1> lazy_exp; // Type expression with lazy evaluation
typedef lambda<lazy_exp>::type lambda_t; // Wrap as lambda metafunction
typedef lambda_t::apply<int_<4> >::type eval_t; // The evaluated type
eval_t e; // Instantiate
e(); // Call evaluation operator

The apply metafunction

Is a special metafunction, which invokes the result of a lambda metafunction. In other words, the apply metafunction is a shorthand notation for the evaluation of the metafunctor wrapped into a lambda metafunction.

template <class Fun, class...Args>
struct apply : lambda<Fun>::type::template apply<Args...> {};

Common techniques

Type definition forwarding

Type definition forwarding can easily be achieved using inheritance. This is usually applied as a shortcut in order to avoid repetition of typedef lines. One can also make use of the fact that different template specialisations can be derived from different base classes.

struct parent_struct {
typedef int type;
}
struct child_struct : parent_struct {};
NiHu::_2
std::integral_constant< int, 2 > _2
shorthand for constant two
Definition: guiggiani_1992.hpp:75
NiHu::_1
std::integral_constant< int, 1 > _1
shorthand for constant one
Definition: guiggiani_1992.hpp:73