Modern Modern Template Techniques • Template The Simplest Function Template • CRTP—Static Polymorphism Techniques • Type Traits—Basic Metaprogramming • Compile-time Conditionals • Policy Classes • Perfect Forwarding • Viewing Deduced Types Jon Kalb Meeting C++ Berlin 2019-11-16
Template Challenge Template Challenge Let’s write a function template from scratch. What is wrong with this? template <class T, class U> Don’t worry it’s the simplest function in the world: add() —it adds two things: T add(T const& a, U const& b) template <class T> { return a + b; } T add(T a, T b) { return a + b; } T is an unknown type so it might be expensive to copy, so: template <class T> T add(T const& a, T const& b) { return a + b; } Easy-peasy, right? The simplest function in the world. Wait, but if we want to add two things of different types? template <class T, class U> T add(T const& a, U const& b) { return a + b; } Meeting C++ Meeting C++ Jon Kalb Jon Kalb 3 2019-11-16 4 2019-11-16
Template Challenge Template Challenge What is wrong with this? What’s wrong with this? template <class T, class U> template <class T, class U> T add(T const& a, U const& b) decltype(T + U) add(T const& a, U const& b) { return a + b; } { return a + b; } What should the return type be? It doesn’t compile! • We can’t assume it is the type of the first parameter. Why not? • We can’t even assume it is the type of either parameter. decltype works on expressions. You can’t add two types. • Adding a char and a short results in an int . How can we fix this? The return type should be whatever you get when you add a T and a U . template <class T, class U> decltype(a + b) add(T const& a, U const& b) How do we say that? { return a + b; } template <class T, class U> decltype(T + U) add(T const& a, U const& b) { return a + b; } Meeting C++ Meeting C++ Jon Kalb Jon Kalb 5 2019-11-16 6 2019-11-16
Template Challenge Template Challenge What’s wrong with this? // C++11 template <class T, class U> template <class T, class U> auto add(T const& a, U const& b) -> decltype(a + b) decltype(a + b) add(T const& a, U const& b) { return a + b; } { return a + b; } It doesn’t compile! This is the simplest template in the world. Why not? But it couldn’t be written in Classic C++ because we need decltype and trailing a and b in decltype(a + b) are not defined. return type function declarations . How can we fix this? Both of these were introduced in C++11 and they are important tools in even template <class T, class U> basic template code. auto add(T const& a, U const& b) -> decltype(a + b) { return a + b; } Meeting C++ Meeting C++ Jon Kalb Jon Kalb 7 2019-11-16 8 2019-11-16
Template Challenge Template Challenge // C++11 // C++14 template <class T, class U> template <class T, class U> auto add(T const& a, U const& b) -> decltype(a + b) auto add(T const& a, U const& b) { return a + b; } { return a + b; } C++14 made it even easier to create templates by allowing us to use type C++14 made it even easier to create templates by allowing us to use type deduction for return types. deduction for return types. Meeting C++ Meeting C++ Jon Kalb Jon Kalb 9 2019-11-16 10 2019-11-16
Modern Template Techniques Modern Template Techniques • • The Simplest Function Template The Simplest Function Template • • CRTP—Static Polymorphism CRTP—Static Polymorphism • • Type Traits—Basic Metaprogramming Type Traits—Basic Metaprogramming • • Compile-time Conditionals Compile-time Conditionals • • Policy Classes Policy Classes • • Perfect Forwarding Perfect Forwarding • • Viewing Deduced Types Viewing Deduced Types
Static Polymorphism CRTP • Polymorphism : • Curiously Recurring Template Pattern: struct derived: base<derived> • a common interface { • defined by a base class ~~~ } • implemented by derived class • Dynamic polymorphism : relies on tools from Object-Oriented Programming. • Does that even compile? • Using a base class pointer (or reference) to a derived class object: • We don’t know actual type at compile time. • Virtual functions • Indirect dispatching at runtime. • Static polymorphism : relies on Curiously Recurring Template Pattern • We know the actual type at compile time • No need for virtual functions or runtime indirection • Allows us to inject behavior into a class without v-table Meeting C++ Meeting C++ Jon Kalb Jon Kalb 13 2019-11-16 14 2019-11-16
CRTP CRTP • Challenge: template <class Derived> struct base • We want to create a base class with an interface that will be implemented { by derived classes, but without virtual functions . void interface() { • We can rely on knowing the type of the derived class at compile time, but // verify pre-conditions, etc we only want to use the inherited interface. static_cast<Derived*>(this)->implementation(); • We want this to type-safe. // verify post-conditions, etc } }; struct derived: base<derived> { void implementation() { /* */ } }; derived d; d.interface(); // Uses the base class interface to get derived behavior. Meeting C++ Meeting C++ Jon Kalb Jon Kalb 15 2019-11-16 16 2019-11-16
CRTP CRTP • The traditional first example from Steve Dewhurst: • Simple example: template <class T> template <class Derived> struct counter struct cloneable { { counter() {++ctr_;} Derived* clone() const counter(counter const&) {++ctr_;} { return new Derived{static_cast<Derived const&>(*this)}; } ~counter() {--ctr_;} }; static long get_count() {return ctr_;} private: struct bar final: cloneable <bar> inline static long ctr_; // inline variables from C++17 { }; bar(int id): id{id} {} int id; struct my_string: counter<my_string> {~~~}; }; my_string a, b{"content"}; my_string c{a}; bar b{42}; bar* my_clone{b.clone()}; std::cout "count: " << my_string::get_count() << "\n"; std::cout << "id: " << my_clone->id << "\n"; count: 3 id: 42 Meeting C++ Meeting C++ Jon Kalb Jon Kalb 17 2019-11-16 18 2019-11-16
CRTP CRTP • More interesting example (thanks to Barton, Nackman, and Dewhurst): template<class T> struct my_complex: eq<my_complex> { template <class T> T real; struct eq T imaginary; { friend bool operator==(T const&a, T const&b) {return a.compare(b) == 0;} bool compare(my_complex const&rhs) const friend bool operator!=(T const&a, T const&b) {return a.compare(b) != 0;} {return (real == rhs.real) and (imaginary == rhs.imaginary);} }; ~~~ }; • Where compare() is defined in the derived class and returns a negative value for less, zero for equals, and a positive value for greater. struct my_string: eq<my_string>, rel<my_string> { template <class T> bool compare(my_string const&rhs) const {return std::strcmp(s, rhs.s);} struct rel ~~~ { friend bool operator<(T const&a, T const&b) {return a.compare(b) < 0;} private: friend bool operator<=(T const&a, T const&b) {return a.compare(b) <= 0;} char* s; friend bool operator>(T const&a, T const&b) {return a.compare(b) > 0;} }; friend bool operator>=(T const&a, T const&b) {return a.compare(b) >= 0;} }; Meeting C++ Meeting C++ Jon Kalb Jon Kalb 19 2019-11-16 20 2019-11-16
CRTP CRTP • Real world example: • Real world example: Solution • In our application, Widgets are always in shared pointers. std::enable_shared_from_this ( a CRTP type) • std::vector<std::shared_ptr<Widget>> // data structure for std::vector<std::shared_ptr<Widget>> // data structure for processedWidgets; // processed Widgets processedWidgets; // processed Widgets struct Widget { struct Widget: std::enable_shared_from_this< Widget > { … … void process() { // Widget-processing function void process() { // Widget-processing function … // process the Widget … // process the Widget processedWidgets.emplace_back(this); // uh oh… processedWidgets.emplace_back(shared_from_this()); } } }; }; • This is a problem waiting to happen. this is a raw pointer, so: • Inherit std::enable_shared_from_this to safely convert this to shared_ptr . • Call to emplace_back creates a control block for *this . • Call to shared_from_this instead of using this . • But there is already at least one std::shared_ptrs pointing to *this . • But how does this work? • So, we have Undefined Behavior. Meeting C++ Meeting C++ Jon Kalb Jon Kalb 21 2019-11-16 22 2019-11-16
Recommend
More recommend