lesson/1.2-metafunctions.cpp (969 lines of code) (raw):
/*
* Copyright (c) 2016, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include <fatal/lesson/driver.h>
namespace lesson {
LESSON(
"decltype()",
"Before we proceed, let's introduce a handy keyword: decltype."
"\n\n"
"This is some magic operator that returns the type of an expression."
"\n\n"
"decltype is very handy when trying to figure out what's the type of, say, a "
"variable, a function or the result of a function call."
) {
COMMENT(
"We can use decltype to figure out what's the type of a literal. It works "
"as if we had typed an alias or a type name directly."
);
CODE(
using a = decltype(10);
using b = decltype(true);
using c = decltype("hello, world");
);
TYPE(a);
TYPE(b);
TYPE(c);
COMMENT(
"We can also use decltype to inspect the type of a variable."
);
CODE(
int x = 20;
bool y = false;
auto z = "test";
using d = decltype(x);
using e = decltype(y);
using f = decltype(z);
);
TYPE(d);
TYPE(e);
TYPE(f);
COMMENT(
"Here we use decltype to check what's the type returned by a function."
);
CODE(
using j = decltype(std::atoi("10"));
);
TYPE(j);
COMMENT(
"But decltype can also be used to check what's the type of the function "
"itself."
);
CODE(
using k = decltype(std::atoi);
);
TYPE(k);
}
/**
* @author: Marcelo Juchem <marcelo@fb.com>
*/
LESSON(
"template instantiations as metafunctions",
"Just like in procedural programming where we can apply functions on values "
"and obtain another value as a result, in metaprogramming we can apply "
"metafunctions on types and obtain another type as a result. The concept is "
"not too different. Only now, instead of manipulating values, we're "
"manipulating types."
"\n\n"
"C++ also allows metafunctions to operate on constants and other "
"metafunctions (higher order metafunctions). Roughly speaking, metafunctions "
"in C++ operate on anything that can be passed as a template parameter."
"\n\n"
"For the purpose of this lesson, we'll not differentiate types and constants "
"as metafunction parameters."
"\n\n"
"Throughout this lesson and the Fatal library we also use the terms "
"\"operation\" and \"transform\" to refer to metafunctions. For the sake of "
"the exercise, let's assume they are all the same thing."
"\n\n"
"The simplest metafunction we can think of is a template instantiation. That "
"is, given that we have a class template, if you will, we can create an "
"actual type by instantiating it after passing proper template arguments."
"\n\n"
"Why is a type template considered a metafunction? Because it takes types as "
"parameters (template arguments) and outputs a type (template instantiation) "
"as the result. This may not make much sense now but, as we will see in a "
"later lesson, making no distinction greatly simplifies things when using "
"higher-order metafunctions.",
template <typename T, typename U>
class foo {
T data1;
U data2;
};
) {
COMMENT(
"`foo` template is a class template, not a class. In other words, it's a "
"type template, not an actual type. In order to stay away from the formal "
"lingo, let's just say that it's impossible to instantiate an object of "
"type `foo`."
);
ILLEGAL(
"can't use an uninstantiated type template as a type",
foo f1;
);
COMMENT(
"In order to obtain an actual type from a type template, we must "
"instantiate it by passing the appropriate template parameters required by "
"the template."
"\n\n"
"Since we're talking about metafunctions, the code below passes two "
"parameters, `int` and `double`, to `foo` and obtains the type "
"`foo<int, double>` as a result."
);
CODE(
using template_instantiation = foo<int, double>;
);
COMMENT(
"Now that we finally have a type, we can instantiate an object with it:"
);
CODE(
template_instantiation f2;
);
COMMENT(
"Or we can skip the alias altogether and instantiate the template and the "
"object in the same expression:"
);
CODE(
foo<int, double> f3;
);
COMMENT(
"Both `f2` and `f3` are variables of the same type: `foo<int, double>`."
"\n\n"
"The code below will be further explained in a later lesson. For now, it "
"suffices to know that it will prevent the program from compiling if both "
"arguments passed to `std::is_same` do not represent the same type."
"\n\n"
"In other words, if this code compiles, then both arguments passed to "
"`std::is_same` refer to exactly the same type."
);
CODE(
static_assert(std::is_same<decltype(f2), decltype(f3)>::value, "mismatch");
);
COMMENT(
"For a more familiar example, let's use the `std::vector` template instead:"
);
ILLEGAL(
"again, can't use an uninstantiated type template as a type",
std::vector v1;
);
COMMENT(
"But it works when we instantiate the template in order to obtain a type. "
"In this case, `std::vector<int>`:"
);
CODE(
using my_list = std::vector<int>;
my_list v2;
std::vector<int> v3;
static_assert(std::is_same<decltype(v2), decltype(v3)>::value, "mismatch");
);
}
/**
* @author: Marcelo Juchem <marcelo@fb.com>
*/
LESSON(
"custom metafunctions",
"In this lesson we will write some simple metafunctions. Some of them will "
"be pretty useless while some will be used by a later lesson to illustrate "
"another concept."
"\n\n"
"The point of this lesson is to demonstrate how to declare and use custom "
"metafunctions by passing parameters and getting results.",
template <typename T>
struct unary {};
template <typename T, typename U>
struct binary {};
template <typename T, typename U, typename V>
struct ternary {};
template <typename... Args>
struct variadic {};
template <typename T, typename U, typename... Args>
struct another_variadic {};
template <typename T>
struct expose_member_named_yyz {
using yyz = T;
};
template <typename First, typename Second>
struct simple_pair {
using first = First;
using second = Second;
};
) {
COMMENT(
"Let's start with a simple template that takes a single parameter and does "
"nothing. It doesn't really return anything so, just for the sake of the "
"exercise, we'll consider the template instantiation itself as being the "
"result of the metafunction."
);
CODE(
using a = unary<int>;
using b = unary<bool>;
using c = unary<int>;
);
TYPE(a);
TYPE(b);
TYPE(c);
COMMENT(
"Note that `a` and `c` pass the same parameters to the metafunction. "
"Template metaprogramming is considered purely functional. Therefore, "
"it is not possible to have side-effects from a call to a metafunction."
"\n\n"
"This means that both `a` and `c` above represent exactly the same type "
"since there's no internal state in the metafunctions that could change "
"between the calls."
"\n\n"
"The static assertion below makes sure of it."
);
CODE(
static_assert(std::is_same<a, c>::value, "mismatch");
);
COMMENT(
"The assertions below are similar, but they will compile if and only if "
"both arguments passed to `std::is_same` do NOT represent the same type."
);
CODE(
static_assert(!std::is_same<a, b>::value, "mismatch");
static_assert(!std::is_same<b, c>::value, "mismatch");
);
COMMENT(
"This is a dummy example, similar to the one above, but demonstrating a "
"metafunction taking two parameters instead of one."
);
CODE(
using d = binary<int, bool>;
using e = binary<bool, int>;
using f = binary<int, bool>;
);
TYPE(d);
TYPE(e);
TYPE(f);
COMMENT(
"Again, subsequent calls will have the same results."
);
CODE(
static_assert(std::is_same<d, f>::value, "mismatch");
static_assert(!std::is_same<d, e>::value, "mismatch");
static_assert(!std::is_same<e, f>::value, "mismatch");
);
COMMENT(
"Below is a third example, just like the ones above. This time it "
"demonstrates a metafunction taking three parameters."
);
CODE(
using g = ternary<int, bool, double>;
using h = ternary<bool, double, int>;
using i = ternary<int, bool, double>;
);
TYPE(g);
TYPE(h);
TYPE(i);
COMMENT(
"Once more, subsequent calls have the same results."
);
CODE(
static_assert(std::is_same<g, i>::value, "mismatch");
static_assert(!std::is_same<g, h>::value, "mismatch");
static_assert(!std::is_same<h, i>::value, "mismatch");
);
COMMENT(
"Below is an example of a variadic metafunction. That means it can take "
"any number of parameters."
"\n\n"
"We'll take a closer look at variadics later. Right now we're only "
"interested in knowing they exist, how to declare and how to use them."
);
CODE(
using j = variadic<>;
using k = variadic<bool>;
using l = variadic<int, double>;
using m = variadic<int, bool, double>;
using n = variadic<bool, void, short, long, double, float, long, bool>;
using o = variadic<int, bool, double>;
);
TYPE(j);
TYPE(k);
TYPE(l);
TYPE(m);
TYPE(n);
TYPE(o);
COMMENT(
"It is still true that subsequent calls have the same results."
);
CODE(
static_assert(std::is_same<m, o>::value, "mismatch");
static_assert(!std::is_same<j, m>::value, "mismatch");
);
COMMENT(
"We can also require a minimum number of parameters by declaring "
"non-variadic parameters for the metafunction:"
);
ILLEGAL(
"`another_variadic` requires at least two arguments to be passed",
using p = another_variadic<>;
using q = another_variadic<bool>;
);
CODE(
using r = another_variadic<int, double>;
using s = another_variadic<int, bool, double>;
using t = another_variadic<bool, void, short, long, double, long, bool>;
using u = another_variadic<int, bool, double>;
);
TYPE(r);
TYPE(s);
TYPE(t);
TYPE(u);
COMMENT(
"Once more, the same parameters yield the same results."
);
CODE(
static_assert(std::is_same<s, u>::value, "mismatch");
static_assert(!std::is_same<s, t>::value, "mismatch");
);
COMMENT(
"A classic example of a variadic template is `std::tuple`."
);
CODE(
using v = std::tuple<>;
using w = std::tuple<bool>;
using x = std::tuple<int, bool, double>;
using y = std::tuple<bool, short, long, double, float, long, bool>;
);
TYPE(v);
TYPE(w);
TYPE(x);
TYPE(y);
COMMENT(
"Sometimes it's useful to return something from the metafunction, other "
"than the template instantiation itself. The easiest way to do that is "
"with a member type alias."
"\n\n"
"The metafunction `expose_member_named_yyz`, as its name suggests, exposes "
"a member named `yyz`. For the sake of this exercise, we will consider "
"this member to represent its result."
"\n\n"
"There's no rule telling how to properly return results from metafunctions "
"so the best bet is to resort to some arbitrary convention. Using member "
"alias templates is one such convention. As long as the intention is made "
"clear and it doesn't hurt API usability, all's good."
"\n\n"
"Granted, `yyz` is not a very good member name, but it's definitely a damn "
"great song, so let's stick with it for now."
"\n\n"
"Since the aim of this lesson is to illustrate how to return results let's "
"not focus on what is returned, just on how to set the result and how to "
"access it from the caller's standpoint."
);
CODE(
using z = expose_member_named_yyz<void>;
);
TYPE(z);
TYPE(z::yyz);
COMMENT(
"There's also the possibility of returning more than one result from a "
"metafunction. This is actually not uncommon in template metaprogramming."
"\n\n"
"The easiest way to accomplish that is still to provide a member type "
"alias for each of the results we want to return."
"\n\n"
"The metafunction `simple_pair` exposes two member type aliases called "
"`first` and `second`. Again, let's not worry about what is returned. For "
"the purpose of this exercise we'll assume `first` and `second` to "
"represent the two results returned by this metafunction."
);
CODE(
using A = simple_pair<short, long>;
);
TYPE(A);
TYPE(A::first);
TYPE(A::second);
}
/**
* @author: Marcelo Juchem <marcelo@fb.com>
*/
LESSON(
"operations on values (1/2)",
"In this lesson we will write metafunctions that operate on numbers. For "
"instance, we can define the four basic arithmetic operations as "
"metafunctions."
"\n\n"
"It may not seem very useful, but this allows us to, as we will see, compose "
"these operations in any way and pass this composition around as new "
"metafunctions. The usefulness of this code will become much clearer when we "
"cover higher-order metafunctions.",
// convenience alias for the arithmetic examples
template <int Value>
using int_val = std::integral_constant<int, Value>;
template <typename LHS, typename RHS>
using add = int_val<LHS::value + RHS::value>;
template <typename LHS, typename RHS>
using subtract = int_val<LHS::value - RHS::value>;
template <typename LHS, typename RHS>
using multiply = int_val<LHS::value * RHS::value>;
template <typename LHS, typename RHS>
using divide = int_val<LHS::value / RHS::value>;
template <typename A, typename B, typename C, typename D>
using composite = multiply<subtract<A, B>, add<C, D>>;
) {
COMMENT(
"Let's start by declaring a few constants, just so our examples don't get "
"too verbose."
);
CODE(
using i3 = int_val<3>;
using i5 = int_val<5>;
using i7 = int_val<7>;
using i10 = int_val<10>;
using i20 = int_val<20>;
);
CONSTANT(i3);
CONSTANT(i5);
CONSTANT(i7);
CONSTANT(i10);
CONSTANT(i20);
COMMENT(
"Similarly to a regular function call, we pass the constants above as "
"parameters to the metafunctions, which in turn return the result as a "
"type."
);
CODE(
using a = add<i5, i10>;
using b = subtract<i3, i5>;
using c = multiply<i3, i7>;
using d = divide<i20, i7>;
);
CONSTANT(a);
CONSTANT(b);
CONSTANT(c);
CONSTANT(d);
COMMENT(
"Metafunctions can also be composed:"
);
CONSTANT(add<add<i5, i10>, i20>);
CONSTANT(subtract<multiply<i20, i3>, add<i5, i10>>);
CONSTANT(multiply<subtract<i20, i10>, add<i3, i3>>);
CONSTANT(divide<i20, add<i3, i3>>);
CONSTANT(
divide<
multiply<
add<i5, i10>,
subtract<i20, i7>
>,
i3
>
);
COMMENT(
"And the composition can be exposed as yet another metafunction, as in "
"`composite`:"
);
CONSTANT(composite<i20, i10, i3, i5>);
}
/**
* @author: Marcelo Juchem <marcelo@fb.com>
*/
LESSON(
"operations on values (2/2)",
"In this lesson we will show an example of how operations on values can be "
"useful for a more practical problem."
"\n\n"
"Specifically, we will determine, at compile time, the size of a buffer as a "
"function of the maximum amount of bytes we want it to take up."
"\n\n"
"We will reuse the metafunctions defined by the previous 'operations on "
"values' lesson.",
template <typename T>
using size_of = int_val<sizeof(T)>;
template <typename LHS, typename RHS>
using maximum = int_val<LHS::value < RHS::value ? RHS::value : LHS::value>;
template <typename T, typename MaxByteSize>
using buffer_size = maximum<int_val<1>, divide<MaxByteSize, size_of<T>>>;
template <typename T, int MaxByteSize>
using buffer = std::array<T, buffer_size<T, int_val<MaxByteSize>>::value>;
struct my_element {
std::int32_t field1;
std::int16_t field2[4];
std::uint64_t field3;
char field4[80];
};
) {
COMMENT(
"We start by writing a metafunction called `size_of`, that calculates the "
"size, in bytes, of a given type."
);
CONSTANT(size_of<char>);
CONSTANT(size_of<double>);
CONSTANT(size_of<std::int16_t>);
CONSTANT(size_of<std::int64_t>);
CONSTANT(size_of<my_element>);
COMMENT(
"We then write a metafunction called `buffer_size` to calculate the "
"maximum number of elements of a given type that will not exceed a given "
"number of bytes."
"\n\n"
"Since we're not interested in a buffer with zero elements, we make an "
"exception for when a single element exceeds the byte threshold."
);
CODE(
using max_byte_size = int_val<90>;
);
CONSTANT(buffer_size<char, max_byte_size>);
CONSTANT(buffer_size<double, max_byte_size>);
CONSTANT(buffer_size<std::int16_t, max_byte_size>);
CONSTANT(buffer_size<std::int64_t, max_byte_size>);
CONSTANT(buffer_size<my_element, max_byte_size>);
COMMENT(
"Finally, we write a metafunction called `buffer` that returns the type of "
"the buffer as a `std::array`, using `buffer_size` to determine the "
"appropriate size."
);
TYPE(buffer<char, 820>);
TYPE(buffer<double, 514>);
TYPE(buffer<std::int16_t, 5150>);
TYPE(buffer<std::int64_t, 2112>);
TYPE(buffer<my_element, 600>);
}
/**
* @author: Marcelo Juchem <marcelo@fb.com>
*/
LESSON(
"standard operations on types",
"Not all metafunctions need to be type templates or operate on constants. In "
"fact, it's very common to have alias templates that operate on types, "
"regardless of whether these represent actual values or not."
"\n\n"
"It will become clearer on later lessons how useful it is to manipulate "
"types that do not represent actual values."
"\n\n"
"For now, it suffices to understand how such operations on types work, in "
"order not to become biased by the examples that use "
"`std::integral_constant`."
"\n\n"
"The standard library already provides a lot of ready-to-use metafunctions. "
"We'll start with these existing operations. They can mostly be found in the "
"`<type_traits>` header."
) {
COMMENT(
"One such metafunction provided by the standard library is called "
"`is_reference`. It receives a type as a parameter and, as its name "
"suggests, returns a `true` or `false` constant telling whether the "
"type is a reference or not."
);
CODE(
using ir1 = std::is_reference<int>;
using ir2 = std::is_reference<int &>;
);
CONSTANT(ir1);
CONSTANT(ir2);
COMMENT(
"Another metafunction, called `add_lvalue_reference`, takes a type as a "
"parameter and returns a l-value reference to this type. If the input type "
"is already an l-value reference, it returns the type itself."
);
CODE(
using r1 = std::add_lvalue_reference<int>;
using r2 = std::add_lvalue_reference<int &>;
using r3 = std::add_lvalue_reference<int const>;
using r4 = std::add_lvalue_reference<int const &>;
);
TYPE(r1);
TYPE(r1::type);
CONSTANT(std::is_reference<r1::type>);
TYPE(r2);
TYPE(r2::type);
CONSTANT(std::is_reference<r2::type>);
TYPE(r3);
TYPE(r3::type);
CONSTANT(std::is_reference<r3::type>);
TYPE(r4);
TYPE(r4::type);
CONSTANT(std::is_reference<r4::type>);
COMMENT(
"There's also a metafunction called `is_signed` which tells whether a type "
"a signed arithmetic type or not."
);
CODE(
using is1 = std::is_signed<int>;
using is2 = std::is_signed<std::uint32_t>;
using is3 = std::is_signed<double>;
using is4 = std::is_signed<std::string>;
);
CONSTANT(is1);
CONSTANT(is2);
CONSTANT(is3);
CONSTANT(is4);
COMMENT(
"It's also widely known that C++ templates are Turing complete. Here's an "
"example of how to write conditional statements that choose one of two "
"types depending on a condition expression."
);
CODE(
using t = std::conditional<true, float, double>;
using f = std::conditional<false, short, long>;
);
TYPE(t);
TYPE(t::type);
TYPE(f);
TYPE(f::type);
COMMENT(
"Finally, we can compose these metafunctions in countless ways."
);
CODE(
using c = std::conditional<
std::is_signed<int>::value,
std::add_lvalue_reference<bool>::type,
void
>;
);
TYPE(c);
TYPE(c::type);
}
/**
* @author: Marcelo Juchem <marcelo@fb.com>
*/
LESSON(
"custom operations on types 1/2",
"We'll now show how to write a few custom metafunctions that operate on "
"types."
"\n\n"
"We will use some of the metafunctions introduced by the previous 'custom "
"metafunctions' lesson.",
template <typename T>
struct get_member_named_yyz {
using result = typename T::yyz;
};
template <typename T>
struct get_type_and_member_named_yyz {
using type = T;
using result = typename T::yyz;
};
template <typename T>
struct another_type_and_yyz {
using type = T;
using result = typename get_member_named_yyz<type>::result;
};
template <typename T>
using simpler_get_yyz = typename T::yyz;
template <typename T>
struct fancier_type_and_yyz {
using type = T;
using result = simpler_get_yyz<type>;
};
) {
COMMENT(
"The metafunction called `get_member_named_yyz`, as its name suggests, "
"looks for a member type alias named `yyz` in the type received as a "
"parameter. It then returns the type represented by that alias as its "
"result."
);
CODE(
using a = expose_member_named_yyz<void>;
using b = get_member_named_yyz<a>;
);
TYPE(b);
TYPE(b::result);
COMMENT(
"The metafunction called `get_type_and_member_named_yyz` returns two "
"results. It exposes the argument it received as a member type alias "
"called `type`. It also exposes its second result as a member type alias "
"called `result`, representing `type::yyz`."
);
CODE(
using c = get_type_and_member_named_yyz<a>;
);
TYPE(c);
TYPE(c::type);
TYPE(c::result);
COMMENT(
"The metafunction called `another_type_and_yyz` does the same thing as "
"`get_type_and_member_named_yyz`, albeit in a different way. It employs "
"another metafunction, `get_member_named_yyz`, to obtain `type::yyz`."
);
CODE(
using d = another_type_and_yyz<a>;
);
TYPE(d);
TYPE(d::type);
TYPE(d::result);
COMMENT(
"Metafunctions don't have to be written as classes or structs. They can "
"also be written as alias templates. Instead of returning results as "
"members, their actual template instantiation represents their results."
"\n\n"
"This allows for a friendlier syntax, much closer to that of a function "
"call in prodcedural languages. We've seen a similar approach in the "
"lesson 'operations on values'."
"\n\n"
"Below we demonstrate `simpler_get_yyz`, which does exactly what "
"`get_member_named_yyz` does, but in the form of an alias template."
);
TYPE(simpler_get_yyz<a>);
COMMENT(
"In the same fashion as `another_type_and_yyz`, `fancier_type_and_yyz` "
"uses `simpler_get_yyz` to obtain `type::yyz`."
);
CODE(
using e = fancier_type_and_yyz<a>;
);
TYPE(e);
TYPE(e::type);
TYPE(e::result);
}
/**
* @author: Marcelo Juchem <marcelo@fb.com>
*/
LESSON(
"custom operations on types 2/2",
"This lesson introduces some metafunctions that perform type manipulation on "
"their parameters.",
template <typename T>
struct make_it_a_pointer {
using pointer = T *;
};
template <typename T>
struct make_it_a_pointer_if_not_already {
using type = typename std::conditional<
std::is_pointer<T>::value,
T,
T *
>::type;
};
template <typename T>
using simpler_make_it_a_pointer_if_not_already = typename std::conditional<
std::is_pointer<T>::value,
T,
T *
>::type;
template <typename T>
struct cleanup_type {
using type = typename std::remove_const<
typename std::remove_reference<T>::type
>::type;
};
) {
COMMENT(
"The metafunction called `make_it_a_pointer`, as its name suggests, "
"converts the input type into a pointer to it."
);
CODE(
using a = make_it_a_pointer<double>;
using b = make_it_a_pointer<short *>;
);
TYPE(a);
TYPE(a::pointer);
TYPE(b);
TYPE(b::pointer);
COMMENT(
"The metafunction called `make_it_a_pointer_if_not_already` is a little "
"smarter and only performs the conversion if the input type is not yet a "
"pointer."
);
CODE(
using c = make_it_a_pointer_if_not_already<double>;
using d = make_it_a_pointer_if_not_already<short *>;
);
TYPE(c);
TYPE(c::type);
TYPE(d);
TYPE(d::type);
COMMENT(
"`simpler_make_it_a_pointer_if_not_already` does the same thing as "
"`make_it_a_pointer_if_not_already`, but in the form of an alias template."
);
TYPE(simpler_make_it_a_pointer_if_not_already<double>);
TYPE(simpler_make_it_a_pointer_if_not_already<short *>);
COMMENT(
"`cleanup_type` aims to remove any references and const-qualifiers from "
"the input type."
);
CODE(
using e = cleanup_type<double>;
using f = cleanup_type<short *>;
using g = cleanup_type<int const>;
using h = cleanup_type<double &>;
using i = cleanup_type<bool &&>;
using j = cleanup_type<float const &>;
using k = cleanup_type<unsigned const &&>;
using l = cleanup_type<long const *const &>;
);
TYPE(e);
TYPE(e::type);
TYPE(f);
TYPE(f::type);
TYPE(g);
TYPE(g::type);
TYPE(h);
TYPE(h::type);
TYPE(i);
TYPE(i::type);
TYPE(j);
TYPE(j::type);
TYPE(k);
TYPE(k::type);
TYPE(l);
TYPE(l::type);
COMMENT(
"`cleanup_type` resembles a much more useful metafunction present in the "
"standard library called `std::decay`. This is a very important "
"metafunction, widely used in metaprogramming."
"\n\n"
"It is worth to take some time to get more familiar with it since we'll "
"need this metafunction it in later lessons: "
"http://en.cppreference.com/w/cpp/types/decay"
);
CODE(
using m = std::decay<double>;
using n = std::decay<short *>;
using o = std::decay<int const>;
using p = std::decay<double &>;
using q = std::decay<bool &&>;
using r = std::decay<float const &>;
using s = std::decay<unsigned const &&>;
using t = std::decay<long const *const &>;
);
TYPE(m);
TYPE(m::type);
TYPE(n);
TYPE(n::type);
TYPE(o);
TYPE(o::type);
TYPE(p);
TYPE(p::type);
TYPE(q);
TYPE(q::type);
TYPE(r);
TYPE(r::type);
TYPE(s);
TYPE(s::type);
TYPE(t);
TYPE(t::type);
}
/**
* @author: Marcelo Juchem <marcelo@fb.com>
*/
LESSON(
"nested metafunctions",
"Sometimes it's desirable to have metafunctions taking several arguments. "
"This can easily hinder usability of the API, therefore a nice solution is "
"needed."
"\n\n"
"There can also be a need to group several related metafunctions together, "
"with a possible intersection on the set of parameters they accept."
"\n\n"
"One way to tackle this problem is to use nested metafunctions. That is, a "
"metafunction that is not limited to exposing results, but also other "
"inner metafunctions that depend on the parameters of the outer one."
"\n\n"
"Nested metafunctions will make more sense once we cover higher-order "
"metafunctions on a later lesson. For now, it suffices to know they are "
"possible and how they are declared."
"\n\n"
"C++ template syntax can be quite daunting, but the examples presented in "
"this lesson are not that complicated. Try to identify what does each member "
"represent and the patterns used to implement them. Several of these "
"patterns are quite recurring.",
template <typename T>
struct nested {
using type = unary<T>;
template <typename U>
using inner = std::pair<T, U>;
};
template <typename T>
struct nested_2 {
using type = unary<T>;
template <typename U>
struct inner {
using type = binary<T, U>;
};
};
template <typename T>
struct nested_3 {
using type = unary<T>;
template <typename U>
struct inner {
using type = binary<T, U>;
template <typename V>
using innermost = ternary<T, U, V>;
};
};
template <typename T, typename U>
struct nested_4_inner {
using type = binary<T, U>;
template <typename V>
using innermost = ternary<T, U, V>;
};
template <typename T>
struct nested_4 {
using type = unary<T>;
template <typename U>
using inner = nested_4_inner<T, U>;
};
) {
COMMENT(
"Let's start by calling the metafunction `nested`. It exposes two things:\n"
"- a member alias called `type`, which we'll consider its result\n"
"- a member metafunction called `inner`"
"\n\n"
"As far as the member `type` is concerned, nothing new here:"
);
CODE(
using a = nested<int>;
);
TYPE(a);
TYPE(a::type);
COMMENT(
"Now, let's take a look at the inner metafunction. It is implemented as an "
"alias template, therefore its result will come directly from its "
"instantiation:"
);
CODE(
using b = a::inner<double>;
);
TYPE(b);
COMMENT(
"Let's also look at `nested_2`, which is a slight variation of `nested`."
"\n\n"
"The only difference between them is that `nested`'s inner metafunction is "
"implemented with an alias template, whereas `nested_2`'s inner "
"metafunction is implemented as a regular class template which exposes its "
"result as a member."
);
CODE(
using c = nested_2<int>;
);
TYPE(c);
TYPE(c::type);
COMMENT(
"So let's look at the application of the inner metafunction and also at "
"its result:"
);
CODE(
using d = c::inner<double>;
);
TYPE(d);
TYPE(d::type);
COMMENT(
"There are pros and cons for each approach. For instance, the first one "
"requires less typing and looks simpler, while the second one allows for "
"more than one result to be returned."
"\n\n"
"The latter also allows exposing yet another level of one or more inner "
"metafunctions, as illustrated by `nested_3`:"
);
CODE(
using e = nested_3<int>;
);
TYPE(e);
TYPE(e::type);
COMMENT(
"Let's inspect `nested_3`'s inner metafunction and its result:"
);
CODE(
using f = e::inner<double>;
);
TYPE(f);
TYPE(f::type);
COMMENT(
"And now let's call the innermost metafunction:"
);
CODE(
using g = f::innermost<bool>;
);
TYPE(g);
COMMENT(
"It may seem too complicated having several nested levels of "
"metafunctions, and indeed it's usually best to avoid it for the same "
"reason one should avoid too many nested levels of if statements in "
"procedural programming: it's best to break the code down into smaller "
"components than to have a large mammoth that does everything in one place."
"\n\n"
"But as far as the technique goes, not much has changed for those familiar "
"with recursion (which one should be when entering the world of template "
"metaprogramming). If we consider `f`, the application of `nested_3`'s "
"inner metafunction, as if it were a separate outer metafunction, it "
"becomes easier to understand. "
"\n\n"
"That's exactly what `nested_4` illustrates below. Note that the usage of "
"`nested_4` is exactly the same as `nested_3`."
);
CODE(
using h = nested_4<int>;
using i = h::inner<double>;
using j = i::innermost<bool>;
);
TYPE(h);
TYPE(h::type);
TYPE(i);
TYPE(i::type);
TYPE(j);
}
} // namespace lesson {