Template Metaprogramming with Modern C++: Introduction

Template Metaprogramming with Modern C++: Introduction

Posted on September 10, 2014 by Manu Sánchez

Template Metaprogramming with Modern C++: Introduction

Any sufficiently complex C++ code is indistinguishable from trolling

Arthur C. Clarke

Preface

Template metaprogramming is one of the things that makes C++ that complex, poor known, and sometimes horrible language. However, its power and expressiveness are some of the best features of C++.

Extensible and fully generic C++ libraries aren’t possible without template metapogramming. Even the Standard Library implementations hide many template metaprogramming tricks to make standard containers and algorithms that generic, high level, and efficient tools we use everyday.

The fact that tmp is a powerful tool can be seen in the evolution of the language, which now has features designed to improve metaprogramming, see C++11 <type_traits>, C++11 variadic templates, C++14 variable templates, C++14std::integer_sequence, etc.

Credit: https://gitorious.org/metatrace

But C++ template metaprogramming power comes at a high cost: Its really hard to do and understand. The template system was not originally designed to do such things, and that’s reflected primarily in the cumbersome syntax involved and the cryptic error messages one get when something fails. Those are the reasons why people is usually scared by tmp, and most of us doesn’t even try to use it.

These posts try to introduce template metaprogramming to the average C++ programmer, showing how it works, what can do, and finally leading with its problems trying to make it easier than in the old days of C++98, thanks to C++11 and C++14 language improvements.


But, what is metaprogramming?

From Wikipedia:

Metaprogramming is the writing of computer programs that write or manipulate other programs (or themselves) as their data, or that do part of the work at compile time that would otherwise be done at run-time.

So instead of writing code that is compiled and does something at run-time (i.e. represents some actions to be done at run-time), we write code (meta-code?) that generates code. Let me show you a simple example:

1

#define MIN(x,y) (((x) > (y)) ? (x) : (y))

C parameterized macros could be viewed as metaprogramming functions, metafunctions. That is, a function that takes some parameters and generates C code. If you use that macro:

1

2

3

4

int main()

{

int a , b , c = MIN(a,b);

}

Please ignore the UB, is just an example.

The C preprocessor parses that macro, interprets its arguments, and returns the code (((a) > (b)) ? (a) : (b)), so the resulting code becomes:

1

2

3

4

int main()

{

int a , b , c = (((a) < (b)) ? (a) : (b));

}

Reflection, the ability of some programming languages to inspect type and code information at runtime and modify it, could be another type of metaprogramming.


C++ Template Metaprogramming

Template metaprogramming, sometimes shorted to tmp, consists in using the C++ template system to generate C++ types, and C++ code in the process.

Consider what a C++ template is: As the name says, it’s only a template. A template function is not a function at all, it is a template to generate functions, and the same for class templates.

That wonderful thing we all love, std::vector, is not a class. Is a template designed to generate a correct vector class for each type. When we instance a template, like std::vector<int>, then the compiler generates the code for a vector of ints, following the template the Standard Library developer provided.

So if we write a template foo parameterized with a type parameter:

1

2

3

4

5

template<typename T>

struct foo

{

T elem;

};

and then that template is instanced:

1

2

typedef foo<int> fooint;

typedef foo<char> foochar;

and the compiler generates different versions of the foo struct, one for each different combinations of template parameters:

1

2

3

4

5

6

7

8

9

10

11

12

struct foo_int

{

int elem;

};

struct foo_char

{

char elem;

};

typedef foo_int fooint;

typedef foo_char foochar;

Note that the generated classes foo_int and foo_char are not written in your source file at all, like what the C preprocessor does. The template instantiation is managed internally by the compiler. I wrote them in that way to make a clear example.

Credit: http://plc.inf.elte.hu/templight/

As you can see, the C++ template system actually generates code. We, as C++ metaprogrammers, explode this to generate some code automatically.


Metafunctions

In the C preprocessor example, we introduced the concept of metafunction. In general a metafunction is a function working in the specific metaprogramming domain we are. In the case of C preprocessor, we manipulate C sourcecode explicitly, so its metafunctions (macros) take and manipulate C source.

In C++ template metaprogramming we work with types, so a metafunction is a function working with types. C++ templates could take non-type parameters too, but its hard to be generic using heterogeneous categories of template parameters. Instead, we will work with type parameters only whenever possible.

1

2

3

4

5

template<typename T>

struct identity

{

using type = T;

};

The template identity is a metafunction representing the identity function: Takes a value (Actually a type, since we work with types) and returns itself untouched.

We can “call” that metafunction referencing its member type type:

1

using t = typename identity<int>::type; // t is int

Of course nested metafunction calls are possible:

1

using t = typename identity<typename identity<int>::type>::type; //t is int

But that typename ::type syntax doesn’t scale well. Consider a more complex example:

1

2

3

using t = typename add<typename add<std::integral_constant<int,1>,std::integral_constant<int,2>>::type,

std::integral_constant<int,-2>

>::type;

There are a few possible solutions to this problem:

Use aliases to the result instead of the metafunction itself

Since C++11 we have template aliases, a kind of parametrized typedef. We can use them to write user-side metafunctions:

1

2

template<typename LHS , typename RHS>

using add = typename impl::add<LHS,RHS>::type;

Where add is the metafunction for the user, and impl::add is the class template that actually implements the metafunction. This allows us to write nested expressions in a clear way:

1

using t = add<std::integral_constant<int,1>,add<std::integral_constant<int,-2>,std::integral_constant<int,-4>>;

Build an expression evaluation system

The above approach hides the machinery to the user. But hidding means that those user side metafunctions are not metafunctions but aliases to their result. That means we cannot use user-side aliases in contexts expecting metafunctions: User-side metafunctions are not first class functions.

Instead, we could build an expression evaluation system which takes an expresssion (A template with its parameters) and evaluate it saying “Is this a metafunction? Ok, so I should get its result via typename ::type. This approach has the advantage that one could customize the evaluation and design it for many complex cases. The simplest one, before evaluating a metafunction evaluate its parameters.

This is what I did for Turbo, and Boost.MPL.Lambda takes a similar approach:

1

2

3

4

5

6

7

8

//https://www.biicode.com/manu343726/manu343726/turbo_core/master

#include "manu343726/turbo_core/turbo_core.hpp"

using tml::placeholders::_1;

using tml::placeholders::_2;

//t is tml::Int<3> (std::integral_constant<int,3>)

using t = tml::eval<tml::lambda<_1,_2 , tml::add<_1,_2>> , tml::Int<1>,tml::Int<2>>;

C++14 variable templates: Stop doing ugly template metaprogramming and use a natural syntax

This last approach is available since C++14 thanks to variable templates. A variable template is a constant parametrized with a template. The canonical example is a pi constant aware of the precision of the type used:

1

2

3

4

5

template<typename T>

constexpr T pi = 3.141592654;

float radious = 1.0f;

float circle  = pi<float>*pi<float>*radious;

Variable templates are values parametrized with templates, instead of types. So we can use constexprfunctions instead of template metafunctions to operate even with types (Imagine a variable template acting as a box for a type).

See Boost.Hanna for an example of this approach.


A Haskell-like language inside C++

Since we work with the C++ type system, using types as values for our computations, tmp works like a functional programming language; because metafunctions have no side effects: We can only create types, not to modify existing ones.
And like in a functional language, one of the pillars of tmp is recursion. In this case recursive template instantiations (Remember that name).

1

2

3

4

5

6

7

8

9

10

11

template<typename T>

struct throw_stars

{

using type = T;

};

template<typename T>

struct throw_stars<T*>

{

using type = typename throw_stars<T>::type;

};

I think the classic factorial/Fibonacci metafunctions examples are so boring. Here is something more interesting: The template throw_stars is a metafunction that takes a type and throws away all the “stars”.

1

using t = typename throw_stars<int********>::type; //t is int

The template specialization acts as the recursive case, and the main template as the base case. Note how C++ template specialization behaves like pattern matching.

Another example could be traversing of C++11 variadic packs:

1

2

3

4

5

6

7

8

9

10

11

12

13

template<typename HEAD , typename... TAIL>

struct last

{

using type = typename last<TAIL...>::type;

};

template<typename T>

struct last<T>

{

using type = T;

};

using t = typename last<int,char,bool,double>::type; //t is double

which is a great example of a head:tail approach for list traversing common in functional languages.


Summary

In this first approach to C++ template metaprogramming we have seen that:

  • Metaprogramming is the process of writing code to generate code, that is, automatize code generation.
  • C++ template metaprogramming uses the template system to generate types, and code in the process: We generate types using templates, and we actually use those types to do computations or to generate the desired code.
  • The basic unit of metaprogramming is the metafunction, as in common programming the basic unit is the function. Metafunctions manipulate entities of their specific metaprogramming domain. In C++ template metaprogramming, those entities are types, and metafunctions are represented through templates.
  • Template metaprogramming is like a functional language embedded into C++ itself. That “language” has no side effects (We cannot modify an existing type, only create new ones), so we use the same patterns as in a functional programming language such as Haskell or F#.

Now we have a good overview of what C++ template metaprogramming is, but we need some C++ knowledge before getting into it.
The next time we will learn C++ templates in depth: Template parameters, template specialization, SFINAE, etc; to make sure we all have and understand the necessary tools to do proper metaprogramming in Modern C++.

Written by Manu Sánchez.

时间: 2024-11-29 07:01:02

Template Metaprogramming with Modern C++: Introduction的相关文章

CppCon - Modern Template Metaprogramming 杂记

2014年底才看到github和channel9上有CppCon2014的视频和资料,顿时激动不已.最近小生也一直在研习CppCon2014中令人兴奋的内容.这篇鄙文就是小生学习了<Modern Template Metaprogramming>之后,有对Unevaluated Operands更深刻的理解,有感而写. C++98标准中的Unevaluated Operands,只有sizeof操作符.C++11又引入了decltype,typeid和noexcept.Unevaluated

《Effective C++》:条款48:认识template元编程

Template metaprogramming(TMP,模板元编程)是编写template-based C++程序,编译的过程.template metaprogramming是用C++写的模板程序,编译器编译出具体化的过程.也就是说,TMP程序执行后,从templates具体化出来C++源码,不再是模板了. TMP有两个作用,一是它让某些事更容易.例如编写STL容器,使用模板,可是存放任何类型元素.二是将执行在运行期的某些工作转移到了编译期.还有一个结果是使用TMP的C++程序可能在其他方面

Effective C++ Item 48 认识 template 元编程

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:Template metaprogramming (TMP, 模板元编程)可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率 示例1: template<typename IterT, typename DistT> void advance(IterT &iter, DistT d){ if(typeid(typename std::iterator_t

《Effective C++》:条款48:理解力template 元编程

Template metaprogramming(TMP,模板元编程)这是写template-based C++规划.编译过程.template metaprogramming随着C++写模板程序,化的过程.也就是说,TMP程序运行后,从templates详细化出来C++源代码.不再是模板了. TMP有两个作用,一是它让某些事更easy. 比如编写STL容器,使用模板,可是存放不论什么类型元素.二是将运行在运行期的某些工作转移到了编译期.另一个结果是使用TMP的C++程序可能在其它方面更高效:较

C++ template —— template metaprogram(九)

metaprogramming含有“对一个程序进行编程”的意思.换句话说,编程系统将会执行我们所写的代码,来生成新的代码,而这些新代码才真正实现了我们所期望的功能.通常而言,metaprogramming这个概念意味着一种反射的特性:metaprogramminig组件只是程序的一部分,而且它也只生成一部分代码或者程序. 使用metaprogramming的目的是为了实现更多的功能,并且是花费的开销(代码大小,维护的开销等来衡量)更小.另一方面,metaprogramming的最大特点在于:某些

C++ template —— 表达式模板(十)

表达式模板解决的问题是:对于一个数值数组类,它需要为基于整个数组对象的数值操作提供支持,如对数组求和或放大: Array<double> x(1000), y(1000); ... x = 1.2 * x + x * y; 对效率要求苛刻的数值计算器,会要求上面的表达式以最高效的方式进行求值.想既高效又以这种比较紧凑的运算符写法来实现,表达式模板可以帮助我们实现这种需求. 谈到表达式模板,自然联想到前面的template metaprogramming.一方面:表达式模板有时依赖于深层的嵌套模

模板Trait 技术与简述template 元编程

模板Trait 技术 想了好久都没有想到合适的例子,已是干脆直接使用[1]中的例子就好了. STL 中引入了迭代器的概念.但是在本文中的例子不直接使用STL 的迭代器,而是写了一段很简短的代码,作为演示使用. 本例中的迭代器有三种: Forward_Iter,只能向前进,也就是只能加非负数 Bidirectional_Iter,可以双向增减 Random_Iter,可以随意增减 *本例并没有沿用STL 中的名称,请注意区别 对于三种的迭代器的偏移,大概如下: template<typename

Metaprogramming

Metaprogramming https://en.wikipedia.org/wiki/Metaprogramming 愿编程, 是一中编程技术, 制造的计算机程序,具有能力, 对待程序为他们的数据. 针对程语言的编程技术. Metaprogramming is a programming technique in which computer programs have the ability to treat programs as their data. It means that a

你应当如何学习C++(以及编程)(转载)

你应当如何学习C++(以及编程)(rev#1) By 刘未鹏(pongba) C++的罗浮宫(http://blog.csdn.net/pongba) Javascript是世界上最受误解的语言,其实C++何尝不是.坊间流传的错误的C++学习方法一抓就是一大把.我自己在学习C++的过程中也走了许多弯路,浪费了不少时间. 为什么会存在这么多错误认识?原因主要有三个,一是C++语言的细节太多.二是一些著名的C++书籍总在(不管有意还是无意)暗示语言细节的重要性和有趣.三是现代C++库的开发哲学必须用