Template Metaprogramming - Practical Approach · code::dive 2016 - C++ Template Metaprogramming -...
Transcript of Template Metaprogramming - Practical Approach · code::dive 2016 - C++ Template Metaprogramming -...
C++ Template MetaprogrammingPractical Approach
code::dive 2016Szymon Gutaj
code::dive 2016 - C++ Template Metaprogramming - Practical Approach2
In brief
● Introduction● Metaprogramming techniques● Questions / Discussion
code::dive 2016 - C++ Template Metaprogramming - Practical Approach3
Introduction
code::dive 2016 - C++ Template Metaprogramming - Practical Approach4
(My Humble) Definition
Shifting complexity from the user/client sideto the library/compiler side
Augmenting the compiler
code::dive 2016 - C++ Template Metaprogramming - Practical Approach5
Aims
● Correctness (reducing human factor)– By generating code according to (tested) rules– By introspecting and checking code at compile time– By creating libraries smarter than their user– By avoiding repetition
code::dive 2016 - C++ Template Metaprogramming - Practical Approach6
Aims
● Performance (self-optimisation)– By performing things at compile time– By generating fine-tuned code in particular situation– By performing optimisations cumbersome to handcraf
code::dive 2016 - C++ Template Metaprogramming - Practical Approach7
Aims
● Portability (self-adaptability)– By automatically adapting to current hardware– By intelligently picking optimal code blocks– By keeping all code in the C++ realm
code::dive 2016 - C++ Template Metaprogramming - Practical Approach8
Aims
● Expressive interfaces– By automatically deducing details for the user– By creating domain-specific languages
code::dive 2016 - C++ Template Metaprogramming - Practical Approach9
Consequences (wink)
● You'll keep your job forever, having written metaprogrammed code(if your colleagues like you)
● You'll be happy to have trained karate for 15 years(if they don't like you)
● Nothing will happen (are you sure, that anyone has seen your commit?)
code::dive 2016 - C++ Template Metaprogramming - Practical Approach10
Consequences
● Development time of your (metaprogrammed) librarydepends on skill and tools used
● Maintenance of your library requires some skill● Your user code becomes higher level● Your user code becomes smaller and more declarative● Changes to your library (bugfixes and new features)
propagate to all places of usage
code::dive 2016 - C++ Template Metaprogramming - Practical Approach11
Approaches
● Handcrafted● With aid of C++ Standard Library (type_traits)● With aid of Boost (Boost Metaprogramming Library, Boost Hana)● With aid of higher level, domain-specific libraries
code::dive 2016 - C++ Template Metaprogramming - Practical Approach12
How it works
● C++ Metaprogramming is purely functional● Mutation is replaced with copying (with changes applied)● Iteration is replaced with recursion● Computational complexity of a metaprogram is measured with the
amount of template instantiations (copies)
code::dive 2016 - C++ Template Metaprogramming - Practical Approach13
Golden rules
● Keep your code orthogonal● Keep your conventions consistent● Solve things by adding one more level of indirection
code::dive 2016 - C++ Template Metaprogramming - Practical Approach14
Handcrafed metaprogrammingBasics and conventions
code::dive 2016 - C++ Template Metaprogramming - Practical Approach15
Disclaimer
Our examples are meant to be fun, easy and demonstrational
code::dive 2016 - C++ Template Metaprogramming - Practical Approach16
Hello world!
1 // This is our metaprogram (actually quite useful!)2 template<typename T>3 struct return_as_is4 {5 typedef T type;6 };7 8 // This is our processed data9 class HelloWorld10 {11 };12 13 // What is the type of ‘helloWorld’? Yes, it’s HelloWorld!!!14 return_as_is<HelloWorld>::type helloWorld;
code::dive 2016 - C++ Template Metaprogramming - Practical Approach17
Operating on types
● Template name is the operation name● Template parameters are the arguments● Nested definition named “type” is the result● The “struct” keyword is used to keep the syntax clean● We've just described a metafunction!
code::dive 2016 - C++ Template Metaprogramming - Practical Approach18
Operating on types
1 // Adds a pointer to given type2 template<typename T>3 struct add_pointer4 {5 typedef T * type;6 };7 8 // ‘pointerToInt’ is of type ‘int *’9 add_pointer<int>::type pointerToInt {nullptr};
code::dive 2016 - C++ Template Metaprogramming - Practical Approach19
Operating on values
● Template name is the operation name● Template parameters are the arguments● Nested member named “value” is the result● Nested member is static const / constepxr
code::dive 2016 - C++ Template Metaprogramming - Practical Approach20
Operating on values
1 // Increments given value by one2 template<int V>3 struct increment4 {5 static constexpr int value = V + 1;6 };7 8 // Variable ‘four’ equals 49 // ‘3 + 1’ happened during compilation10 int four = increment<3>::value;
code::dive 2016 - C++ Template Metaprogramming - Practical Approach21
Operating on values
1 // Wraps an integer in a type2 template<3 typename T,4 T Value5 >6 struct integral_constant 7 {8 typedef integral_constant type;9 10 static constexpr T value = Value;11 };12 13 // Dressing values as types can be useful14 typedef integral_constant<int, 1> one;15 typedef integral_constant<int, 2> two;
code::dive 2016 - C++ Template Metaprogramming - Practical Approach22
Simple processing
● Pattern matching is used for “flow control”● Patterns are defined with partial template spacialisations
code::dive 2016 - C++ Template Metaprogramming - Practical Approach23
Simple processing1 template< // Primary template: “true” case2 bool Condition,3 typename TrueType,4 typename FalseType5 >6 struct if_7 {8 typedef TrueType type; // TrueType is the result9 };10 11 template< // Partially specialised: “false” case12 typename TrueType,13 typename FalseType14 >15 struct if_<false, TrueType, FalseType>16 {17 typedef FalseType type; // FalseType is the result18 };
code::dive 2016 - C++ Template Metaprogramming - Practical Approach24
Simple processing#include <cstdint> // For INT8_MAX, int8_t and int_least16_t
#include "wild_wild_world/constants.h" // SOME_CONSTANT comes from here
typedef if_< (SOME_CONSTANT < INT8_MAX), int8_t, int_least16_t>::type smallest_type;
// ‘myConstant’ has the smallest type, that fits the valuesmallest_type myConstant = SOME_CONSTANT;
code::dive 2016 - C++ Template Metaprogramming - Practical Approach25
Simple processing
1 #include "configuration.h" // For WANT_SPEED2 #include "algorithm_speedy.h" // For algorithm_speedy3 #include "algorithm_tiny.h" // For algorithm_tiny4 5 typedef if_<6 WANT_SPEED,7 algorithm_speedy,8 algorithm_tiny9 >::type algorithm;10 11 algorithm a;12 13 a.run(); // Runs the selected algorithm
code::dive 2016 - C++ Template Metaprogramming - Practical Approach26
Simple recursion
● Pattern matching with a “terminal case” allow for recursion● Patterns are defined with partial template spacialisations
code::dive 2016 - C++ Template Metaprogramming - Practical Approach27
Simple recursion1 template<typename T>2 struct rank // Terminal case: T is not a table anymore3 {4 static constexpr int value = 0;5 };6 template<typename T, int N>7 struct rank<T[N]> // Case: T is a table8 {9 static constexpr int value = rank<T>::value + 1;10 };11 template<typename T>12 struct rank<T[]> // Case: T is a table (of unknown size)13 {14 static constexpr int value = rank<T>::value + 1;15 };16 auto rank0 = rank<std::string>::value; // Equals 017 auto rank1 = rank<int[2][4]>::value; // Equals 218 auto rank2 = rank<int[4][3][]>::value; // Equals 3
code::dive 2016 - C++ Template Metaprogramming - Practical Approach28
Metafunction forwarding
● Inheritance is a neat way to compose metaprograms● Code and conventions are automatically propagated
code::dive 2016 - C++ Template Metaprogramming - Practical Approach29
Metafunction forwarding
1 // ‘false_type’ is an alias for ‘bool’ equal ‘false’2 struct false_type :3 integral_constant<4 bool,5 false,6 > {};7 8 // ‘true_type’ is an alias for ‘bool’ equal ‘true’9 struct true_type :10 integral_constant<11 bool,12 true,13 > {};
code::dive 2016 - C++ Template Metaprogramming - Practical Approach30
Metafunction forwarding
1 template<typename T>2 struct is_byte_sized :3 if_<4 (sizeof(T) == 1),5 true_type,6 false_type7 > {};8 9 bool byteSized = is_byte_sized<char>::value; // true1011 bool notByteSized = is_byte_sized<int>::value; // false
code::dive 2016 - C++ Template Metaprogramming - Practical Approach31
Lazy evaluation
● Lazy evaluation gives more flexibility● Invalid, but unneeded branches of code still compile● Lazy evaluation leads to faster code● Stay lazy, whenever you can
code::dive 2016 - C++ Template Metaprogramming - Practical Approach32
Lazy evaluation
1 // Eager!!!2 template<typename T>3 struct my_favourite :4 if_<5 is_good_enough<T>::value,6 take_it<T>::type, // 'take_it' always evaluated7 fix_it<T>::type // 'fix_it' always evaluated8 > {};9 10 // Lazy...11 template<typename T>12 struct my_favourite :13 if_<14 is_good_enough<T>::value,15 take_it<T>, // 'take_it' evaluated for ‘true’16 fix_it<T> // 'fix_it' evaluated for ‘false’17 >::type {};
code::dive 2016 - C++ Template Metaprogramming - Practical Approach33
Toolset
● Compile-time operators are the low-level means for code introspection– sizeof, decltype, alignof, ternary operator, template template parameters, ...
● Language rules are your worst enemy and best friend– resolution/conversion rules, SFINAE, variadic templates, ...
code::dive 2016 - C++ Template Metaprogramming - Practical Approach34
Toolset
1 struct NeighbourRomek // A well behaved guy2 {3 void howdy();4 };5 6 struct ManInBlack // Well, who knows...7 {8 };9 10 // Let’s check contents of some types11 bool doesnt = has_howdy<int>::value; // false12 13 bool does = has_howdy<NeighbourRomek>::value; // true14 15 bool afraidToCheck = has_howdy<ManInBlack>::value; // ?!
code::dive 2016 - C++ Template Metaprogramming - Practical Approach35
Toolset
1 template<typename T, void(T::*)() = &T::howdy>2 char hasHowdy(int); // Helper: SFINAE sensitive3 4 template<typename T>5 long hasHowdy(...); // Helper: less sensitive6 7 // Returns ‘true’ if ‘T’ has a member function ‘howdy’8 template<typename T>9 struct has_howdy :10 integral_constant<11 bool,12 sizeof(char) == sizeof(hasHowdy<T>(0))13 > {};
code::dive 2016 - C++ Template Metaprogramming - Practical Approach36
Toolset
1 template<typename T> // Helper: matches a pointer 2 true_type hasHowdy(decltype(&T::howdy));3 4 template<typename T>5 false_type hasHowdy(...); // Helper: matches everything6 7 // Returns ‘true’ if ‘T’ has a member function ‘howdy’8 template<typename T>9 struct has_howdy :10 decltype(hasHowdy<T>(nullptr))11 {};
code::dive 2016 - C++ Template Metaprogramming - Practical Approach37
Homework
● Metafunction classes (first step to passing metafunctions around)● Higher order metafunctions (composed metafunctions)● Tag based dispatch (adding a second level of abstraction)● Variadic template parameter pack access (very useful)
code::dive 2016 - C++ Template Metaprogramming - Practical Approach38
Asertive code
● Assertive code makes error messages better● Assertive code prevents abuse of your metaprogram● Use “static_assert”● Use “std::is_same” or alike● Wait for a standardised C++ feature: concepts!
code::dive 2016 - C++ Template Metaprogramming - Practical Approach39
Testing
● Can be done in a regular cpp file● Compile that file with your code for early testing
code::dive 2016 - C++ Template Metaprogramming - Practical Approach40
Testing
1 #include <type_traits>2 #include "pass_optimally.h" // Code under test34 static_assert( // Test case 15 std::is_same< // More on ‘std::is_same’ in a minute6 pass_optimally<VeryBigClass>::type,7 VeryBigClass const &8 >::value,9 "Expected passing large arguments by reference"10 );11 static_assert( // Test case 212 std::is_same<13 pass_optimally<int>::type,14 int15 >::value,16 "Expected passing small arguments by value"17 );
code::dive 2016 - C++ Template Metaprogramming - Practical Approach41
Asertive code
1 template<class Agent, class Handler, class ...Args>2 void Base::call(Handler Agent::*handler, Args&&... args)3 {4 static_assert( // Check if calculated ‘handler’ and ‘args’ match5 std::is_constructible< // More on this in a second6 void(Agent::*)(Args...),7 decltype(handler)8 >::value,9 "Calling with bad arguments."10 "Args need to match the signature of Handler"11 );12 call(std::bind( // If assertion passed, make the call13 handler,14 static_cast<Agent * const>(this),15 std::forward<Args>(args)...)16 );17 }
code::dive 2016 - C++ Template Metaprogramming - Practical Approach42
Standard Template Librarytype_traits
code::dive 2016 - C++ Template Metaprogramming - Practical Approach43
Features
● Basic type instrospection:– is_integral, is_pointer, is_function, is_class, ...– is_same, is_base_of, is_move_constructible, is_convertible,
has_virtual_destructor, ...● Basic type processing:
– add_cv, remove_reference, make_signed, ...● Basic metaprogramming facilities:
– conditional, integral_constant, enable_if, result_of, ...
code::dive 2016 - C++ Template Metaprogramming - Practical Approach44
Example – type_traits
1 #include <type_traits>2 // Return ‘as is’ for unsigned types3 template<typename T>4 typename std::enable_if<5 std::is_unsigned<T>::value, // The condition6 T // The return type7 >::type alwaysPositive(T t) { return t; }8 9 // Return absolute and unsigned10 template<typename T> 11 typename std::enable_if<12 !std::is_unsigned<T>::value, // The condition13 typename std::make_unsigned<T>::type // The return type14 >::type alwaysPositive(T t)15 {16 return (t < 0) ? t : t;17 }
code::dive 2016 - C++ Template Metaprogramming - Practical Approach45
Boost Metaprogramming LibraryBoost MPL
code::dive 2016 - C++ Template Metaprogramming - Practical Approach46
Features
● Mimicks C++ STL features at compile time● Containers and views:
– vector, list, map, set, string, ...● Algorithms:
– fold, transform, sort, copy, find, count, partition, ...● Functional processing (including higher order):
– lambda, quote, bind, apply, if_, ...● Basic metafunctions:
– and_, or_, not_, bitand_, less, min, ...● Basic runtime interfacing:
– for_each, c_str, ...
code::dive 2016 - C++ Template Metaprogramming - Practical Approach47
Example – Boost MPL
1 tuple<int, char const*, bool> myTuple; // Poor man’s tuple2 get<int>(myTuple) = 123; // Access elements by type3 get<char const*>(myTuple) = "hello";45 // (Implementation) Holds a tuple element of type T6 template<typename T>7 struct tuple_field8 {9 T field;10 };1112 // (Implementation) Accesses a tuple element by type13 template<typename T>14 T & get(tuple_field<T> & t)15 {16 return t.field;17 }
code::dive 2016 - C++ Template Metaprogramming - Practical Approach48
Example – Boost MPL
1 #include <boost/mpl/empty_base.hpp>2 #include <boost/mpl/fold.hpp>3 #include <boost/mpl/inherit.hpp>4 #include <boost/mpl/placeholders.hpp>5 #include <boost/mpl/vector.hpp>67 using namespace boost;8 using namespace boost::mpl::placeholders;910 // Composes a tuple, inheriting tuple_field’s11 template<typename ...Fields>12 struct tuple :13 mpl::fold< // The algoritm: fold/accumulate14 mpl::vector<Fields...>, // The data15 mpl::empty_base, // The initial value16 mpl::inherit<_1, tuple_field<_2>> // The operation17 >::type {};
code::dive 2016 - C++ Template Metaprogramming - Practical Approach49
Boost Hana
code::dive 2016 - C++ Template Metaprogramming - Practical Approach50
Features
● Can do compile-time computation (like Boost MPL)● Can do runtime computation (like STL)● Can do heterogeneous computation (like Boost Fusion)● Requires a (decent) C++14 compiler
code::dive 2016 - C++ Template Metaprogramming - Practical Approach51
Example – Boost Hana
1 #include <boost/hana.hpp>2 using namespace boost;3 struct Fish { std::string name; };4 struct Cat { std::string name; };5 struct Dog { std::string name; };67 auto animals = hana::make_tuple(8 Fish{"Nemo"}, Cat{"Garfield"}, Dog{"Snoopy"}9 );10 // Applies a (heterogenous) algorithm in runtime11 auto names = hana::transform(animals, [](auto a) {12 return a.name;13 });1415 assert(hana::reverse(names) ==16 hana::make_tuple("Snoopy", "Garfield", "Nemo")17 );
code::dive 2016 - C++ Template Metaprogramming - Practical Approach52
Example – Boost Hana
1 auto animal_types = hana::make_tuple(2 hana::type_c<Fish*>,3 hana::type_c<Cat&>,4 hana::type_c<Dog*>5 );6 // Applies an algorithm in compile time7 auto animal_ptrs = hana::filter(animal_types, [](auto a) {8 return hana::traits::is_pointer(a);9 });1011 static_assert(animal_ptrs == hana::make_tuple(12 hana::type_c<Fish*>, hana::type_c<Dog*>), ""13 );
code::dive 2016 - C++ Template Metaprogramming - Practical Approach53
Thank you!
code::dive 2016 - C++ Template Metaprogramming - Practical Approach54
Questions?