Metal

Metal is a single header C++11 library designed to make you love template metaprogramming.

There is a myriad of C++ metaprogramming libraries out there so why Metal?

Overview

#include <metal.hpp>
// First we need some Values
union x { char payload[10]; };
class y { public: char c; };
struct z { char c; int i; };
// ... from which we construct some Lists
using l0 = metal::list<>;
// Lists are versatile, we can check their sizes...
static_assert(metal::size<l0>::value == 0, "");
static_assert(metal::size<l1>::value == 1, "");
static_assert(metal::size<l2>::value == 2, "");
static_assert(metal::size<l3>::value == 3, "");
// retrieve their elements...
static_assert(metal::same<metal::front<l3>, x>::value, "");
static_assert(metal::same<metal::back<l3>, z>::value, "");
// count those that satisfy a predicate...
// We can create new Lists by removing elements...
static_assert(metal::same<l0, l0_>::value, "");
static_assert(metal::same<l1, l1_>::value, "");
static_assert(metal::same<l2, l2_>::value, "");
// by reversing the order of elements...
// by transforming the elements...
// even by sorting them...
template<class x, class y>
using smaller = metal::number<(sizeof(x) < sizeof(y))>;
// that and much more!

Definitions

Template metaprogramming may be seen as a language of its own right, it shares the usual syntax of C++ templates, but has its own unique semantics. Because constructs assume different meanings in its context it is useful to define a few key concepts.

Value

Values are the objects of metaprogramming.

Requirements

Any type is a Value.

Examples

using val = int;
using val = decltype(3.14);
struct val { /*...*/ };

Counterexamples

int not_a_val;
auto not_a_val = 3.14;
template<class...>
struct not_a_val { /*...*/ };

See Also

metal::value, metal::is_value

Number

A Number is a compile-time representation of a numerical value.

Requirements

num is a model of Number if and only if num is a specialization of metal::number.

Tip
metal::number<n> is guaranteed to be an alias template to std::integral_constant<metal::int_, n>.

Examples

using num = metal::false_;
using num = metal::number<-1>;
using num = metal::number<'a'>;

Counterexamples

struct not_a_num :
{};
using not_a_num = std::integral_constant<int, 0>;

See Also

metal::number, metal::is_number, metal::as_number

Expression

Expressions, also called metafunctions, are mappings over the set of Values.

Requirements

expr is a model of Expression if and only if expr is a class, union or alias template that only expects Values as arguments.

Examples

template<class... vals>
using expr = metal::number<sizeof...(vals)>;
template<class x, class y>
struct expr;

Counterexamples

template<template<class...> class...> // non-type parameter
struct not_an_expr;
template<int v> // non-type parameter
using not_an_expr = metal::number<v>;

Lambda

Lambdas, short for Lambda Expressions, are first-class Expressions. As Values themselves, Lambdas can serve both as argument as well as return value to other Expressions and Lambdas, thus enabling higher-order composition.

Requirements

lbd is a model of Lambda if and only if lbd is a specialization of metal::lambda.

Examples

template<class T>
using expr = T*;
using lbd = metal::lambda<expr>;

See Also

metal::lambda, metal::is_lambda, metal::as_lambda

List

A List is a sequence of Values.

Requirements

list is a model of List if and only if list is a specialization of metal::list.

Examples

using list = metal::list<>; // an empty list

Counterexamples

using not_a_list = std::tuple<int, int*, int&>; // not a specialization of metal::list

See Also

metal::list, metal::is_list, metal::as_list

Pair

A Pair is a couple of Values.

Requirements

A Pair is any List whose size is 2.

Examples

Counterexamples

using not_a_pair = std::pair<int, unsigned>; // not a List

See Also

metal::pair, metal::is_pair, metal::as_pair

Map

A Map is a collection of unique Values, each of which associated with another Value.

Requirements

A Map is a List of Pairs, whose first elements are all distinct, that is

[[k0, v0], ..., [kn, vn]]; ki != kj for all i, j in {0, n} and i != j

Examples

using m = metal::list<>; // an empty map

Counterexamples

using not_a_map = metal::list< // repeated keys
>;
using not_a_map = metal::list< // not a list of pairs
>;

See Also

metal::map, metal::is_map, metal::as_map

Examples

Note
In the following examples, IS_SAME(X, Y) is just a terser shorthand for static_assert(std::is_same<X, Y>{}, "").

Parsing Raw Literals


If you ever considered augmenting std::tuple, so that instead of the rather clunky std::get<N>()

static_assert(std::get<1>(std::tuple<int, char, double>{42, 'a', 2.5}) == 'a', "");

one could use the more expressive subscript operator [N]

static_assert(AugmentedTuple<int, char, double>{42, 'a', 2.5}[1] == 'a', "");

you might have come up with something like this

constexpr auto operator [](std::size_t i)
-> std::tuple_element_t<i, std::tuple<T...>>& {
return std::get<i>(*this);
}

only to realize the hard way that this is simply not valid C++.

‍error: non-type template argument is not a constant expression

While the keyword constexpr tells the compiler the value returned by operator [] might be a compile time constant, it imposes no such constraint on its arguments, which may as well be unknown at compile-time. It might seem we are out of luck at this point, but let us not forget that long before C++ had constexpr variables, integral constants strictly known at compile time could be expressed with the help of non-type template parameters.

So how about refactoring operator [] to take an instance of metal::number and relying on template pattern matching to extract its non-type template argument?

template<class... T>
struct AugmentedTuple :
std::tuple<T...>
{
using std::tuple<T...>::tuple;
template<metal::int_ i>
constexpr auto operator [](metal::number<i>)
return std::get<i>(*this);
}
};
static_assert(AugmentedTuple<int, char, double>{42, 'a', 2.5}[metal::number<1>{}] == 'a', "");

That looks promising, but then again metal::number<1>{} is even clunkier than std::get<1>(), we want something more expressive.

A custom literal operator that constructs Numbers out of integer literals could help reducing the verbosity

static_assert(AugmentedTuple<int, char, double>{42, 'a', 2.5}[1_c] == 'a', "");

but how is operator ""_c implemented?

It might be tempting to try something like this

constexpr auto operator ""_c(long long i)
return {};
}

but let us not forget the reason why we got this far down the road to begin with, recall we can't instantiate a template using a non-constexpr variable as argument!

All is not lost however, because we can still parse raw literals, in other words, we are in for some fun!

The Raw Literal Operator Template

Raw literal operator templates in C++ are defined as a nullary constexpr function templated over char...

template<char... cs>
constexpr auto operator ""_raw()
-> metal::numbers<cs...> {
return {};
}

where cs... are mapped to the exact characters that make up the literal, including the prefixes 0x and 0b

IS_SAME(decltype(371_raw), metal::numbers<'3', '7', '1'>);
IS_SAME(decltype(0x371_raw), metal::numbers<'0', 'x', '3', '7', '1'>);

as well as digit separators

IS_SAME(decltype(3'7'1_raw), metal::numbers<'3', '\'', '7', '\'', '1'>);

The operator ""_c

We start by defining the literal operator _c as a function that forwards the raw literal characters as a List of Numbers to parse_number and returns a default constructed object of whatever type it evaluates to, which is guaranteed to be a Number in this case.

template<char... cs>
constexpr auto operator ""_c()
-> metal::eval<parse_number<metal::numbers<cs...>>> {
return {};
}

Resolving the Radix

In its turn parse_number strips the prefix, if any, thus resolving the radix, then forwards the remaining characters to parse_digits, which is in charge of translating the raw characters to the numerical values they represent. The radix and digits are then forwarded to assemble_number, which adds up the individual digits according to the radix.

template<class tokens>
struct parse_number {};
template<class... tokens>
struct parse_number<metal::list<tokens...>> {
using type = assemble_number<metal::number<10>, parse_digits<metal::list<tokens...>>>;
};
template<class... tokens>
struct parse_number<metal::list<metal::number<'0'>, tokens...>> {
using type = assemble_number<metal::number<8>, parse_digits<metal::list<tokens...>>>;
};
template<class... tokens>
struct parse_number<metal::list<metal::number<'0'>, metal::number<'x'>, tokens...>> {
using type = assemble_number<metal::number<16>, parse_digits<metal::list<tokens...>>>;
};
template<class... tokens>
struct parse_number<metal::list<metal::number<'0'>, metal::number<'X'>, tokens...>> {
using type = assemble_number<metal::number<16>, parse_digits<metal::list<tokens...>>>;
};
template<class... tokens>
struct parse_number<metal::list<metal::number<'0'>, metal::number<'b'>, tokens...>> {
using type = assemble_number<metal::number<2>, parse_digits<metal::list<tokens...>>>;
};
template<class... tokens>
struct parse_number<metal::list<metal::number<'0'>, metal::number<'B'>, tokens...>> {
using type = assemble_number<metal::number<2>, parse_digits<metal::list<tokens...>>>;
};

Notice how we are able to use template pattern matching and partial template specializations to extract all relevant information from the tokens.

Parsing Digits

Before translating characters to their corresponding numerical values, we need to get rid of all digit separators that may be in the way. To do that we'll use metal::remove, which takes a List l and a Value val and returns another List that contains every element in l and in the same order, except for those that are the same as val.

The remaining characters can then be individually parsed with the help of metal::transform, which takes a Lambda lbd and a List l and returns another List that contains the Values produced by the invocation of lbd for each element in l.

[lbd(l[0]), lbd(l[1]), ..., lbd(l[n-2]), lbd(l[n-1])]

Notice how characters are translated to their actual numerical representation.

Thus we have

Assembling Numbers

We now turn to assemble_number. It takes a List of digits and adds them up according to the radix, in other words

D0*radix^(n-1) + D1*radix^(n-2) + ... + D{n-2}*radix + D{n-1}

which can also be written recursively

((...((0*radix + D0)*radix + D1)*...)*radix + D{n-2})*radix + D{n-1}

This is the equivalent of left folding a List, or, in Metal parlance, metal::accumulate, after its run-time counterpart in the standard library.

using radix = metal::number<10>;
using digits = metal::numbers<3, 7, 1>;
template<class x, class y>
using lbd = metal::lambda<expr>;

Here we introduced a new Expression expr from which we created a Lambda, but we could also have chosen to use bind expressions instead.

Tip
If bind expressions look scary to you, don't panic, we will exercise Expression composition in our next example. Here it suffices to keep in mind that bind expressions return anonymous Lambdas, just like std::bind returns anonymous functions, and that metal::_1 and metal::_2 are the equivalents of std::placeholders.

Finally

Fun With operator ""_c

IS_SAME(decltype(01234567_c), metal::number<342391>); //octal
IS_SAME(decltype(123456789_c), metal::number<123456789>); //decimal
IS_SAME(decltype(0xABCDEF_c), metal::number<11259375>); //hexadecimal

It also works for very long binary literals.

IS_SAME(
decltype(0b111101101011011101011010101100101011110001000111000111000111000_c),
);

And ignores digit separators too.

IS_SAME(decltype(1'2'3'4'5'6'7'8'9_c), metal::number<123456789>);

Church Booleans


Church Booleans refer to a mathematical framework used to express logical operation in the context of lambda notation, where they have an important theoretical significance. Of less practical importance in C++, even in the context of template metaprogramming, they will nevertheless help us acquaint with bind expressions in this toy example.

The boolean constants true_ and false_ are, by definition, Lambdas that return respectively the first and second argument with which they are invoked.

using true_ = metal::_1;
using false_ = metal::_2;

Now, using the fact that booleans are themselves Lambdas, it's not too hard to realize that invoking a boolean with arguments <false_, true_> always yields its negation.

template<class b>
IS_SAME(not_<true_>, false_);
IS_SAME(not_<false_>, true_);

However, to enable higher-order composition we really need not_ to be a Lambda, not an Expression. Granted one could easily define former in terms of the latter as metal::lambda<not_>, but that would defeat the whole purpose of this exercise, the idea is to use bind expressions directly.

Admittedly a little more verbose, but that saves us from introducing a new named alias template.

Using a similar technique, we can also define operators and_ and or_.

This exercise might me mind-boggling at first, but you'll get used to it soon enough.

Without further ado we present the logical operator xor.

Notice how we bind not_, which is only possible due to the fact it is a Lambda.

Automatic Test Cases Generation


Suppose you have a component you want to test, say an algorithm on sequences. Because it's a generic algorithm, it's able to work with any kind of containers and, as such, is specialized for different categories of iterators so that it always delivers optimal performance. Moreover, to provide exception safety guarantees without compromising on execution time or memory consumption, it must also specialize on noexcept properties of the elements, particularly of their move constructors. Finally, it's conceivable that such a generic algorithm could also take advantage of other properties, such as whether elements can be ordered or not.

As such a complex implementation, it ought to be thoroughly tested for all relevant combinations of iterator categories and interesting properties implemented by the contained elements. Furthermore, it's crucial that all corner cases are covered, such as empty sequences to name the most obvious, so, before we even start to get fancy with our test cases, we already have three dimensions that vary independently, namely

  1. Iterator category;
  2. Element properties;
  3. Size of the sequence.

How can we possibly implement this testing suite?

First of all, it's a good idea to subdivide each test case into three steps: the set-up phase constructs the particular sequence to be tested, the execution phase runs our algorithm and the tear-down phase releases the resources. This way we can leverage on RAII and reduce the boilerplate to a minimum.

template<class Container, class Type, class Size>
struct test_case {
test_case() : sequence(Size{}) { /* set-up */ }
~test_case() { /* tear-down */ }
void operator ()() { /* algorithm(this->sequence) */ }
};

That's a good start, but in order to run all test cases we still have to manually instantiate each and every combination of iterator categories, element type and sequence size. That is, if we want to test for M different categories of iterators, N sets of interesting properties that could be provided by elements and O different sizes of containers, we end up with M*N*O distinct instantiations to maintain! That's too troublesome and prone to error, there ought to be a way to generate all test cases automatically.

Fortunately Metal can do just that for us. With the help of metal::cartesian, we can generate every test case automatically, which we can then run by instantiating a single driver class. It's actually very simple.

template<class... Cases>
struct test_cases : Cases... {
void operator()() {
// call operator () on every base class
void(std::initializer_list<int>{(static_cast<Cases*>(this)->operator()(), 0)...});
}
};
template<class Containers, class Types, class Sizes>
auto generate_test_cases()
>
> {
return {};
}
using containers = metal::list<
>;
using types = metal::list<A, /* ..., */ Z>;
using sizes = metal::numbers<0, /* ..., */ MAX_SIZE>;
void test() {
auto cases = generate_test_cases<containers, types, sizes>(); // generate all test cases
cases(); // run
}

And that was it.

To verify that we are in fact generating all possible combinations of test cases under the hood, let's inspect the return type of generate_test_cases

A Word on SFINAE-Friendliness


An Expression is said to be SFINAE-friendly when it is carefully designed so as never to prevent the SFINAE rule to be triggered. In general, such Expressions may only trigger template substitution errors at the point of declaration of a type, which includes the instantiation of alias templates, default template arguments and the signature of function templates. SFINAE-friendly Expressions are exceedingly powerful, because they may be used to drive overload resolution, think std::enable_if on steroids. For this reason, all Expressions in Metal are guaranteed to be strictly SFINAE-friendly.

Conversely, a SFINAE-unfriendly Expression produces so called hard errors, which require the compilation to halt immediately. Examples of hard errors are failed static_assert'ions or template substitution errors at the point of definition of a class or function template. SFINAE-unfriendly Expressions are very inconvenient, because they force compilation to halt when they are not selected by overload resolution, thereby hindering the usage of the entire overloaded set.

make_array

To illustrate how useful SFINAE-friendliness can be, suppose we need a factory function make_array that takes an arbitrary number of arguments and returns a std::array. Because arrays are homogeneous collections, we need the common type of all its arguments, that is, the type to which every argument can be converted to.

The base case is straightforward.

template<class... Xs,
class R = std::array<std::common_type_t<Xs...>, sizeof...(Xs)>
>
constexpr R make_array(Xs&&... xs) {
return R{{std::forward<Xs>(xs)...}};
}
IS_SAME(decltype(make_array(42, 42L, 42LL)), std::array<long long, 3>);

Now suppose we need an array of tuples

using namespace std::chrono;
using namespace std::literals::chrono_literals;
using namespace std::literals::complex_literals;
auto tup1 = std::make_tuple(42ns, 0x42, 42.f);
auto tup2 = std::make_tuple(42us, 042L, 42.L);
auto tup3 = std::make_tuple(42ms, 42LL, 42.i);
auto array_of_tuples = make_array(tup1, tup2, tup3);

‍error: no matching function for call to 'make_array'

Even though the common tuple is really just a tuple of common types, std::common_type_t is unable to find it in general. That means we need to overload make_array and handle the array of tuples case.

make_array of tuples

The idea is to define a metafunction that computes the common tuple from a set of tuples and then use it to overload our factory function.

This sounds like a use-case for Boost.Hana, let's try it.

template<class... xs>
using hana_common_tuple_t = typename decltype(
boost::hana::unpack(
boost::hana::zip_with(
boost::hana::template_<std::common_type_t>,
boost::hana::zip_with(boost::hana::decltype_, std::declval<xs>())...
),
boost::hana::template_<std::tuple>
)
)::type;
template<class... Xs,
class R = std::array<std::common_type_t<Xs...>, sizeof...(Xs)>
>
constexpr R hana_make_array(Xs&&... xs) {
return R{{std::forward<Xs>(xs)...}};
}
template<class Head, class... Tail,
class R = std::array<hana_common_tuple_t<std::decay_t<Head>, std::decay_t<Tail>...>, 1 + sizeof...(Tail)>
>
constexpr R hana_make_array(Head&& head, Tail&&... tail) {
return R{{std::forward<Head>(head), std::forward<Tail>(tail)...}};
}

It works as expected for std::tuples

auto array_of_tuples = hana_make_array(tup1, tup2, tup3);
IS_SAME(decltype(array_of_tuples), std::array<std::tuple<nanoseconds, long long, std::complex<double>>, 3>);

but we get a compilation error as soon as we try to make an array of anything that is not a Boost.Hana Sequence, even though the first overload remains available and would be a perfect match otherwise.

IS_SAME(decltype(hana_make_array(42, 42L, 42LL)), std::array<long long, 3>);

‍error: static_assert failed "hana::zip_with(f, xs, ys...) requires 'xs' and 'ys...' to be Sequences"

make_array of tuples done right

The reason why Boost.Hana can't help us overload make_array is the fact that it doesn't provide any SFINAE-friendliness guarantees, which essentially means that it cannot be used effectively to control overload resolution. Metal, on the other hand, was carefully designed to never trigger hard errors but rather substitution failures, which makes it able to select candidates from an overloaded set by means of the SFINAE rule.

Let's try the same approach using Metal.

template<class... xs>
using common_tuple_t = metal::apply<
std::common_type_t<metal::lambda<std::tuple>, metal::as_lambda<xs>...>,
>;
template<class... Xs,
class R = std::array<std::common_type_t<Xs...>, sizeof...(Xs)>
>
constexpr R make_array(Xs&&... xs) {
return R{{std::forward<Xs>(xs)...}};
}
template<class Head, class... Tail,
class R = std::array<common_tuple_t<std::decay_t<Head>, std::decay_t<Tail>...>, 1 + sizeof...(Tail)>
>
constexpr R make_array(Head&& head, Tail&&... tail) {
return R{{std::forward<Head>(head), std::forward<Tail>(tail)...}};
}

This time it works not only for std::tuple's

auto array_of_tuples = make_array(tup1, tup2, tup3);
IS_SAME(decltype(array_of_tuples), std::array<std::tuple<nanoseconds, long long, std::complex<double>>, 3>);

but also for numerical values

IS_SAME(decltype(make_array(42, 42L, 42LL)), std::array<long long, 3>);

Again, this only works as expected because of the strict SFINAE-friendliness guarantees provided by Metal.

Quick Start

  1. Download metal.hpp
  2. # include </path/to/metal.hpp>
  3. Love template metaprogramming

Migrating from Boost.MPL

A quick glance Metal and Boost.MPL might look very similar, but because Metal leverages modern language features that were not available at the time Boost.MPL was developed, they are in fact fundamentally distinct.

Metafunctions

The representation of metafunctions has been completely redesigned in Metal. Instead of expressing them as class templates that define a nested typename type, Metal assumes metafunctions to be templates, usually but not necessarily alias, that evaluate directly to the result type.

That is, instead of something like this

struct na {};
template<class arg_1 = na, class arg_2 = na, /*...*/ class arg_n = na>
struct metafunction
{
typedef /*...*/ type;
};
template<class arg_1, class arg_2, /*...*/ class arg_n>
struct compound_metafunction
{
typedef typename metafunction<
typename metafunction<arg_1>::type,
typename metafunction<arg_2>::type,
/*...*/
typename metafunction<arg_n>::type
>::type type;
};

you should simply write this

template<class... args>
using metafunction = /*...*/;
template<class... args>
using compound_metafunction = metafunction<metafunction<args>...>;

Notice that traditional lazy metafunctions are still valid Expressions in Metal, but keep in mind that their nested typename type is never implicitly evaluated.

IS_SAME(metal::invoke<metal::lambda<std::add_pointer>, void>, std::add_pointer<void>);
IS_SAME(metal::invoke<metal::lambda<std::common_type>, void*, char[]>, std::common_type<void*, char[]>);

Don't worry, you can still use lazy metafunctions with Metal just fine, you just need to use the adaptor metal::lazy instead of metal::lambda.

IS_SAME(metal::invoke<metal::lazy<std::common_type>, void*, char[]>, void*);

Additionally, you can also explicitly evaluate lazy values using metal::eval.

IS_SAME(metal::eval<std::add_pointer<void>>, void*);
IS_SAME(metal::eval<std::common_type<void*, char[]>>, void*);

Type Traits

Traditionally, type traits are represented as class templates that define a nested integral constant value. Metal on the other hand defines a type trait as any Expression that returns a Number, but that's not to say you can't use good old type traits with Metal just fine, on the contrary, in a similar fashion to lazy metafunctions, all you have to do is use the trait adaptor metal::trait instead of metal::lambda.

Alternatively, you can also explicitly convert traits to Numbers using metal::as_number.

IS_SAME(metal::as_number<std::is_pointer<void*>>, metal::true_);
IS_SAME(metal::as_number<std::is_pointer<void()>>, metal::false_);
IS_SAME(metal::as_number<std::is_convertible<char[], void*>>, metal::true_);
IS_SAME(metal::as_number<std::is_convertible<void*, char[]>>, metal::false_);

Metafunction Classes

The concept of Metafunction Class became obsolete with Metal. Instead of defining first-class metafunctions like you would with Boost.MPL

struct first_class_metafunction
{
template<class arg_1, class arg_2, /*...*/ class arg_n>
struct apply {
typedef /*...*/ type;
};
};

you just have to wrap regular metafunctions using metal::lambda instead

template<class... args>
using metafunction = /*...*/;
using first_class_metafunction = metal::lambda<metafunction>;

It's that simple.

Frequently Asked Questions

What are some advantages of Metal with respect to Boost.MPL?


The most apparent advantage of Metal with respect to Boost.MPL is the fact Metal Lists and Maps can easily exceed the hundreds and even thousands of elements with little impact to the compiler performance, whereas Boost.MPL Sequences, such as mpl::vector and mpl::map, are hard-limited to at most a couple dozen elements and even then at much longer compilation times and higher memory consumption than Metal. Another obvious improvement in Metal is the much terser syntax made possible by alias templates, which were not available at the time Boost.MPL was developed.

Visit metaben.ch for up to date benchmarks that compare Metal against Boost.MPL and other notable metaprogramming libraries. For a brief discussion about fundamental design differences between Boost.MPL and Metal, refer to Migrating from Boost.MPL.

What are some advantages of Metal with respect to Boost.Hana?


As a tool specifically designed for type level programming, Metal is able to provide stronger guarantees and much faster compilation times than Boost.Hana when used for similar purposes. In fact, Metal guarantees SFINAE-friendliness, whereas Boost.Hana does not. Check out A Word on SFINAE-Friendliness for a real world example of the limitations of Boost.Hana with this respect.

Moreover, since Metal concepts are defined by their type signatures, it is always safe to use template pattern matching on them to partially specialize class templates or overload function templates. In contrast, the types of most Boost.Hana objects are left unspecified.

Why isn't std::integral_constant a Number in general?


Numbers are defined as a specific specialization of std::integral_constants, whose binary representation is fixed to metal::int_, an implementation-defined integral type. This design choice stems from the fact two Values compare equal if and only if they have the same type signature. As Values themselves, Numbers are also subject to this requirement, thus had Numbers been defined as a numerical value plus its binary representation, would two Numbers only compare equal if they had both the same numerical value and the same binary representation. This is unreasonable in the context of metaprogramming, where the binary representation of numerical values is entirely irrelevant.