TMP
- Move computations from run-time to compile-time
- Code generation
- Enable self-adapting code (portability, efficiency)
- Facilitate domain-specific programming
- Express complex software patterns and concepts in a more straightforward way
Conventions
- Numeric results
::value
- Type result
::type
template <typename T> struct SizeOfT {
enum { value = sizeof(T) };
};
template <typename T> struct TPtr {
typedef T* type;
};
Template args can be types or numbers
template <size_t N> struct IsEven {
enum { value = (N %2 ) ? 0 : 1 };
};
Metafunctions
remove_cv
NOT tools of metaprogramming
- No variables
- Which means no for loops: use recursion
constexpr
- Purely numeric computations almost always quicker with
constexpr
constexpr
also has recursion depth limits but higher than templates and faster: gcc template recusion depth 10000- Use precomputed static values when it's simpler (pi)
if then else
- No loops but
std::conditional
template <size_t N> struct IsPower2 {
enum { value = (N & (N - 1)) == 0 };
};
static_assert(IsPower2<N>::value, "N must be 2^n");
remove_ptr
returns the type if it's not a pointer
is_pointer
is_reference
has_member_function
std::integral_constant // wrap constant with its type
std::bool_constant // template alias
true_type and false_type are called nullary metafunctions because they have no params.
true_type::value
false_type::value
SFINAE
Substitution failure is not an error: this is where compiler errors go to hide.
Argument non-evaluation
- Operands of sizeof, decltype, declval, alignof, typeid and noexcept are never evaluated
- Not even at compile time
constexpr
constexpr
silently decays to not-compile time unless you use it in a context
that needs to be known at compile time: within square brackets.
Definition
See CPP reference.
A template is a C++ entity that defines one of the following:
- A family of classes (class template), which may be nested classes
- A family of functions (function template), which may be member functions
- An alias to a family of types (alias template) (since C++11)
- A family of variables (variable template) (since C++14)
- A concept (constraints and concepts) (since C++20)
Metafunctions
- Technically a class with zero+ template params and zero+ return types and values
- Convention is that a metafunction should return one thing (like a regular function)
Example traits
Cpp17UnaryTypeTrait
- Class template
- One template argument (mostly)
- Cpp17DefaultConstructible
- Cpp17CopyConstructible
- Publiclly and unambiguously derived from a specialisation of std::integral_constant
Cpp17UnaryTypeTrait
- As above
- But two arguments
Cpp17TransformationTrait
- Class template
- one template arg
- Define a publicy accessible nested type name "type"
- No default/copy constructible req
- No inheritance req
remove_const
Undefined behaviour
- Do not specialise standard type traits
- be careful when using incomplete types
Type metafunctions
- Workhorse (expectially with advent of constexpr)
- Returns a type
std::type_identity
(C++20)- value metatfunctions use variable templates ending with "_v"
Explicit spectialisation
template <> // explicit speciailisation
Printing the type
std::cout << typeid(Type1).name() << 'n';
Compiler support
Now most compilers implement traits with intrinsics.
Primary type categories (traits)
All inherit from either true_type
or false_type
, and all should yield the
same result in light of cv qualifiers.
For any given type T, exactle one of the primary type categories shall yield
true_type
.
is_void
is_null_pointer
is_integral
is_floating_point
is_array
etc
Equals combinatorial explosion!
So...
Metafunction abstractions
- Treat metafunction programming like regular programming
blah_t
is invoking blah
Function overload resolution
- decltype: compiler, don't call this function but what type would it return?
- declval: gives a reference to something as if you had created one
- tag despatch
Comparisons with auto in C++20
auto
uses the same rules as template type paramsauto
will never deduce a reference- constness will be deduced
- decltype(auto) will deduce a reference (should only see in very special cases)
- Generally prefer auto (according to Jason Turner)
- Value types are always copies
These generate the same code:
template <typename T>
T func(T t) {
return t;
}
auto func2(auto t) {
return t;
}
See Jason Turner.
Other C++ standards
- International standard on Mathematical Special Functions in C++
- C++ template metaprogramming uses template instatiation to drive compile-time evalulation
- Template metapgrammamers exploit this machinary to improve a) source code flexibility b) runtime performance
- at compile time can't rely on mutabilty, virtual functions (runtime dispatch), other RTTI, no variables etc: not hard but a difference in mindset... no runtime!
- Call syntax is a "request for the published value"
template<int N>
struct abs {
static_assert (N != INT_MIN); // 2s comp
static constexpr int value = (N < 0) ? -N : N; // initialise value not assign, takes place of return
};
// call
abs<n>value // instantiation yields a complile-time constant
Compile time recursion: metafunction that calls itself
template <unsigned M, unsigned N>
struct gcd {
static int const value = gcd<N, M%N>::value; // per Euclid
};
Suppply base case with a specialisation
template <unsigned M>
struct gcd<M, 0>{
static_assert (M!=0);
static int const value = M;
};
What's the relationship between constexpr functions and templates?
- Familiar syntax
- metafunction is a struct -- can make things public with a struct (not a constexpr function)
- Structs offer public member function decclarations
- Can write a template that takes a type as an argument: e.g., size of can take a type
See also Walter E. Brown.
Identity metafunction
template <class T>
struct type_is {using type = T};
- std::conditional -- compile time IF
- one case you can have a type for true or nothing otherwise: enable_if
enable_if<false, ...>::type -- always an error? No! Welcome to SFINAE
value
void
is one of the 15 types that C++ describes.
is_void<T>::value // since TR1
bool(is_void<T>{}) // instantiate/cast C++11
is_void<T>{}() // variable templates C++14
is_void_v<T> // value returning metafunctions C++17
using true_type = bool_constant<true>;
using false_type = bool_constant<false>;
Concepts
- Solid math foundation: solid, well-baked idea
integral_constant
: inherit from provides more optionsbool_constant
First parameter packs
template <class T, class... P0toN>
struct is_one_of; // declaration not implementation
// base cases
struct is_one_of<T> : false_type{};
struct is_one_of<T, T, P1toN...> : true_type{};
struct is_one_of<T, P0, P1toN>
: is_one_of<T, P1toN...>{};
Not evaluated
sizeof
, typeid
, decltype
and noexcept
are never evaluated, not even at compile time.
- No code is generated
- Don't need a definition
decltype( foo(declval<T>{}) )