Traits policies functors tags Traits policies tags etc

  • Slides: 32
Download presentation
Traits, policies, functors, tags

Traits, policies, functors, tags

Traits, policies, tags, etc. � Traits § Class/struct template not designed to be instantiated

Traits, policies, tags, etc. � Traits § Class/struct template not designed to be instantiated into objects; contents limited to: § § � § § Non-template class/struct, usually not instantiated Compile-time equivalent of objects, containing types/constants/run-time functions Passed as template argument to customize the behavior of the template Functor § § § � Used as a compile-time function which assigns types/constants/run-time functions to template arguments Policy class § � type definitions (via typedef/using or nested struct/class) constants (via static constexpr) static functions Class/struct containing non-static function named operator() Usually passed as run-time argument to function templates Functor acts as a function, created by packing a function body together with some data stored or referenced in the body (closure) Tag class § § § Empty class/struct Passed as run-time argument to function templates Used to carry a compile-time information by their types themselves § Classes/structs are distinguished by their name, not by contents

Policy class � Policy class § § Non-template class/struct, usually not instantiated Compile-time equivalent

Policy class � Policy class § § Non-template class/struct, usually not instantiated Compile-time equivalent of objects, containing types/constants/run-time functions Passed as template argument to customize the behavior of the template § template< typename P> class container { public: container(std: : size_t n) { m_ = P: : alloc(n); /*. . . */ } private: typename P: : pointer m_; }; struct my_policy { using pointer = void*; static pointer alloc(std: : size_t s) { /*. . . */ } }; container< my_policy> k; � Motivation: § § § Policy class allows to pass several types/constants/functions as one argument Policy class is the only way to pass a function as a template argument Policy classes are distinguished by name, not by contents § This could be an advantage or a disadvantage, depending on context

Etymology � Trait [FR] § � Action of firing a projectile § � Un

Etymology � Trait [FR] § � Action of firing a projectile § � Un trait noir. [A black line. ] Characteristic facial lines § � Animaux de trait. [Draft animals. ] Line drawn in one movement § � Le javelot est une arme de trait. [The javelin is a thrown weapon. ] Traction § � From latin tractus Elle a de jolis traits. [She has pretty curves. ] Characteristic of a person, a thing § Traits saillants d’une rencontre. [Highlights of a meeting. ] � The term “trait” is used in psychology and evolutional biology § � The set of psychological/evolutional properties of an individual is termed “traits” From there, it was acquired in programming, almost always as “traits”: § The set of compile-time properties of a programming language item (usually a type) NPRG 041 Programming in C++ - 2016/2017 David Bednárek 4

Traits � Traits § Class/struct template not designed to be instantiated into objects; contents

Traits � Traits § Class/struct template not designed to be instantiated into objects; contents limited to: § § § Used as a compile-time function which assigns types/constants/run-time functions to template arguments Most frequently declared with one type argument § § � type definitions (via typedef/using or nested struct/class) constants (via static constexpr) static functions Used to retrieve information related to the type Example: std: : numeric_limits<T> contains constants and functions describing the properties of a numeric type T Conventions and syntactic sugar § When a traits contains just one type, the type is named “type” § C++11: Usually made accessible directly via template using declaration named “. . . _t” template< typename T> using some_traits_t = typename some_traits< T>: : type; § When a traits contains just one constant, the constant is named “value” § C++14: Usually made accessible directly via template variable named “. . . _v” template< typename T> inline constexpr some_type some_traits_v = some_traits< T>: : value;

Traits - example � Traits are useful when implementing a template acting on unknown

Traits - example � Traits are useful when implementing a template acting on unknown type § std: : numeric_traits<T>: : lowest() returns the minimal (finite) value of a numeric type template< typename T> T vector_max(const std: : vector<T> & v) { T m = std: : numeric_traits<T>: : lowest(); for (auto && a : v) m = std: : max(m, a); return m; } § This example has too narrow interface – a better version uses iterators: § Another traits required to determine the element type: template< typename IT> std: : iterator_traits<IT>: : value_type range_max(IT b, IT e) { using T = std: : iterator_traits<IT>: : value_type; T m = std: : numeric_traits<T>: : lowest(); for (; b != e; ++b) m = std: : max(m, *b); return m; } § There is a dirty trick (not really recommended) to avoid the duplicity of code: template< typename IT, typename T = std: : iterator_traits<IT>: : value_type> T range_max( IT b, IT e) { T m = //. . . }

std: : iterator_traits § § � Container-manipulation functions usually use iterators in their interface

std: : iterator_traits § § � Container-manipulation functions usually use iterators in their interface Such functions need to know some properties of the underlying containers If IT is an iterator type, std: : iterator_traits<IT> contains the following types: § difference_type – a signed type large enough to hold distances between iterators § § § value_type – the type of an element pointed to by the iterator reference – a type acting as a reference to an element § § this is the type actually returned by operator* of the iterator usually value_type& or const value_type& it may be a class simulating a reference (e. g. for vector<bool>) pointer – a type acting as a pointer to an element § § usually std: : ptrdiff_t value_type*, const value_type*, or a class simulating a pointer iterator_category – one of predefined tags describing the category of the iterator § § std: : input_iterator_tag, std: : output_iterator_tag, std: : forward_iterator_tag, std: : bidirectional_iterator_tag, or std: : random_access_iterator_tag shall be used via template specialization or using std: : is_same_v

Tag class � Tag class § § Empty class/struct A tag class acts like

Tag class � Tag class § § Empty class/struct A tag class acts like a compile-time enumeration constant § § � Two use cases § Tag classes are used as type arguments to templates or member types of a class § § § Example: the tags used for iterator_category In this case, a tag class is never instantiated into object, it is also usually empty Tag classes are used as parameters of a function § § § � Unlike an enum type, the set of tag classes may be independently extended It is limited to compile-time, therefore there is no need to assign unique numbering The tag class is instantiated into an empty runtime object (usually optimized out by the compiler) This allows to distinguish between different functions of the same name (e. g. constructors) Examples later. . . In advanced cases, tag classes are templates § They are used to carry the “values” of their template arguments

std: : iterator_traits § Implemented in standard library as template< typename IT> struct iterator_traits

std: : iterator_traits § Implemented in standard library as template< typename IT> struct iterator_traits { using difference_type = typename IT: : difference_type; using value_type = typename IT: : value_type; using reference = typename IT: : reference; using pointer = typename IT: : pointer; using iterator_category = typename IT: : iterator_category; }; § Any class intended to act as an iterator must define the five types referenced above § The five types shall be accessed only indirectly through std: : iterator_traits § Since raw pointers may act as iterators, there is a partial specialization: template< typename T> struct iterator_traits<T*> { using difference_type = std: : ptrdiff_t; using value_type = std: : remove_cv_t<T>; using reference = T&; using pointer = T*; using iterator_category = std: : random_access_iterator_tag; }; § std: : remove_cv_t<T> removes any const/volatile modifiers from T

std: : remove_cv_t � The implementation of std: : remove_cv_t<T> § Based on the

std: : remove_cv_t � The implementation of std: : remove_cv_t<T> § Based on the traits template std: : remove_cv<T> § general template< typename T> struct remove_cv { using type = T; }; § partial specializations have higher priority if they match more precisely the actual argument template< typename T> struct remove_cv< const T> { using type = T; }; template< typename T> struct remove_cv< volatile T> { using type = T; }; template< typename T> struct remove_cv< const volatile T> { using type = T; }; § The result is represented by a member named “type” by convention, used directly as: typename remove_cv<X>: : type § For convenience, the result may be accessed using the type alias: template< typename T> using remove_cv_t = typename remove_cv<T>: : type; § § “_t” suffix convention is widely used in std library It can be used simply as: remove_cv_t<X>

volatile � volatile § Used to denote “non-memory” locations in address space (e. g.

volatile � volatile § Used to denote “non-memory” locations in address space (e. g. I/O ports) § § It is UNSUITABLE for communication between threads § § § Compilers never eliminate or reorder accesses to volatile locations A read from a volatile variable that is modified by another thread without synchronization or concurrent modification from two unsynchronized threads is undefined behavior due to a data race. Use std: : atomic<T> instead Unless you program device drivers or embedded systems, you shall not use volatile § § Nevertheless, your templates shall work even for volatile types Always use std: : remove_cv_t instead of std: : remove_const_t

decltype() and std: : remove_reference_t � Technically, std: : iterator_traits are no longer needed

decltype() and std: : remove_reference_t � Technically, std: : iterator_traits are no longer needed § � It is still usually simpler to use them Replacing std: : iterator_traits with decltype() template< typename IT> auto range_max(IT b, IT e) { using T = std: : remove_cv_t<std: : remove_reference_t<decltype(*b)>>; T m = std: : numeric_traits<T>: : lowest(); for (; b != e; ++b) m = std: : max(m, *b); return m; } � decltype(E) denotes the type of the expression E § § § More exactly: The return type declared for the outermost function invoked in E This is the (compile-time) static type, see typeid for the (run-time) dynamic type In the example, decltype(*b) denotes the return type of IT: : operator* § � This is usually T& or const T& decltype(E) must usually be used with remove_reference_t and remove_cv_t § § in the correct order! const T& -> remove_reference_t -> const T -> remove_cv_t -> T

decltype() and std: : declval � We use the ability of the compiler to

decltype() and std: : declval � We use the ability of the compiler to infer the return type from the body template< typename IT> auto range_max(IT b, IT e) { using T = std: : remove_cv_t<std: : remove_reference_t<decltype(*b)>>; T m = std: : numeric_traits<T>: : lowest(); for (; b != e; ++b) m = std: : max(m, *b); return m; } � What if we wanted to specify the return type explicitly? § § e. g. , in a standalone declaration Using the “auto f() -> T” syntax, we can reference the argument names template< typename IT> auto range_max(IT b, IT e) -> std: : remove_cv_t<std: : remove_reference_t<decltype(*b)>>; § Otherwise, we need std: : declval<T>() § It creates an expression of type T from nothing (by casting nullptr to T*) template< typename IT> std: : remove_cv_t<std: : remove_reference_t<decltype(*std: : declval<IT>())>> range_max(IT b, IT e); § std: : declval is a library template function while decltype is a keyword

std: : is_reference_v � Traits returning constants, e. g. std: : is_reference_v<T> § Based

std: : is_reference_v � Traits returning constants, e. g. std: : is_reference_v<T> § Based on the traits template std: : is_reference<T> § general template< typename T> struct is_reference<T> : std: : false_type {}; § partial specialization has higher priority template< typename T> struct is_reference<T*> : std: : true_type {}; § Uses two type aliases (logically acting as policy classes): using false_type = std: : integral_constant<bool, false>; using true_type = std: : integral_constant<bool, true>; § These are aliases of a particular case of the auxiliary class: template< typename U, U v> struct integral_constant { static constexpr U value = v; //. . . there are more members here. . . explanation later }; § § The result is represented by a static constexpr member named “value” by convention For convenience, the result may be accessed using the global variable alias: template< typename T> inline constexpr is_reference_v = is_reference<T>: : value;

std: : is_reference_v � Use of (Boolean) constants in templates – important examples: template<

std: : is_reference_v � Use of (Boolean) constants in templates – important examples: template< typename T> class example { static constexpr is_ref = std: : is_reference_v< T>; § passing a constant to another template type (possibly specialized) using another_type = some_template< is_ref>; § std: : conditional_t is a compile-time conditional expression acting on types: using my_type = std: : conditional_t< is_ref, std: : add_pointer_t< std: : remove_reference_t< T>>, // replace reference by pointer T>; void a_method() { § constexpr if § no runtime cost; the inactive branch is not semantically checked if constexpr (is_ref) { /*. . . */ } else { /*. . . */ } § passing a constant to a template function some_function< is_ref>(); § passing a type representing a constant to a function via a runtime argument § it creates an object from the traits class (it shall no longer be called traits in this case) using is_ref_t = std: : is_reference<T>; another_function( is_ref_t{}); } };

Value-less function arguments § passing a type representing a constant to a function via

Value-less function arguments § passing a type representing a constant to a function via a runtime argument using is_ref_t = std: : is_reference<T>; another_function( is_ref_t{}); § an empty object is created from the traits class § § § no run-time value is passed through the argument (compilers usually produce no code for it) the argument is used to pass compile-time information, i. e. its type the function may be overloaded on the type § in the case of std: : is_reference<T>, inheritance hierarchy also applies (this is slicing!) void another_function( std: : false_type) { /*. . . */ } void another_function( std: : true_type) { /*. . . */ } § alternatively, the function may be a template< bool v> void another_function( std: : integral_constant< bool, v>) { if constexpr (v) { /*. . . */ } else { /*. . . */ } } § Trick: std: : integral_constant<T, v> also defines conversion operator to T returning v template< typename X> void another_function( X a) { if constexpr (a) { /*. . . */ } else { /*. . . */ } } § This allows defining the function as lambda: auto another_function = [](auto a) { if constexpr (a) { /*. . . */ } else { /*. . . */ }; }

Tag arguments � Distinguishing constructors § § Another use-case for value-less function arguments All

Tag arguments � Distinguishing constructors § § Another use-case for value-less function arguments All constructors have the same name § the name cannot be used to specify the required behavior § Example: std: : optional<T> can store T or nothing using string_opt = std: : optional< std: : string>; string_opt x; // initialized as nothing assert(!x. has_value()); string_opt y(std: : in_place); // initialized as std: : string() assert(y. has_value() && (*y). empty()); string_opt z(std: : in_place, “Hello”); // initialized as std: : string(“Hello”) assert(z. has_value() && *z == “Hello”); § Implementation: struct in_place_t {}; // a tag class inline constexpr in_place_t in_place; // an empty variable of tag type template< typename T> class optional { public: optional(); // initialize as nothing template< typename. . . L> optional( in_place_t, L &&. . . l); }; // initialize by constructing T from the arguments l

Tag arguments � The same approach is also used for regular functions § �

Tag arguments � The same approach is also used for regular functions § � The purpose is to have the same name for different implementations of the same functionality Example: std: : for_each allows to select parallel execution: std: : for_each( std: : execution: : par, k. begin(), k. end(), [](auto && a){ ++a; }); § § std: : execution: : par is a global variable of type std: : execution: : parallel_policy The parallel implementation of for_each: template< typename IT, typename F> void for_each( std: : execution: : parallel_policy, IT b, IT e, F f);

Tag arguments with parameters � A tag class may carry a compile-time value §

Tag arguments with parameters � A tag class may carry a compile-time value § Example: std: : variant<T 1, . . . , Tn> can stores one of T 1, . . . , Tn using my_variant = std: : variant< std: : string, const char *>; my_variant x( in_place_index<0>, “Hello”); // initialized as std: : string(“Hello”) assert(x. index() == 0 && std: : get<0>(x) == “Hello”); my_variant y( in_place_index<1>, “Hello”); // initialized as (const char *)(“Hello”) assert(y. index() == 1 && !strcmp(std: : get<1>(y), “Hello”)); § Implementation: template<std: : size_t> struct in_place_index_t {}; // a tag class template<std: : size_t I> inline constexpr in_place_index_t<T> in_place_index; template< typename. . . TL> class variant { public: template< std: : size_t<T>, typename. . . L> variant( in_place_index_t<T>, L &&. . . l); }; // an empty variable of tag type

Traits, policies, tags, etc. � Functor § Class/struct containing non-static function named operator() §

Traits, policies, tags, etc. � Functor § Class/struct containing non-static function named operator() § § § Usually passed as run-time argument to function templates § § Example: std: : map receives a functor representing a comparison function Functor acts as a function, created by packing a function body together with some data referenced in the body (closure) § § § Example: std: : sort receives a functor representing a comparison function For class templates, a functor becomes both a template argument to the class and a value argument to its constructor § § Usually non-template, but template cases exists (std: : less<T>) Since C++11, mostly created by the compiler as a result of a lambda expression Functionality (i. e. the function implementation) selected at compile-time by template instantiation mechanism Functionality parameterized at run-time by the data members of the functor object If there is no data member in the functor, the functionality is equivalent to a policy class containing a static function § However, the functor must be instantiated and passed as an object since operator() must not be static

Tag classes NPRG 041 Programming in C++ - 2016/2017 David Bednárek 21

Tag classes NPRG 041 Programming in C++ - 2016/2017 David Bednárek 21

Employing type non-equivalence � template< typename P> Type non-equivalence § class Value { Two

Employing type non-equivalence � template< typename P> Type non-equivalence § class Value { Two classes/structs/unions/enums are always considered different § double v; //. . . § Two instances of the same template are considered different if their parameters are different § It also works with empty classes }; struct mass {}; § struct energy {}; � Value< energy> e; To distinguish types which represent different things using the same implementation § e = m; // error Called tag classes Usage: § Value< mass> m; even if they have the same contents § § Physical units Indexes to different arrays Similar effect to enum class

Employing type non-equivalence template< std: : size_t v> � class const_v {}; Templated tag

Employing type non-equivalence template< std: : size_t v> � class const_v {}; Templated tag classes § § template< std: : size_t v> Carriers for compile-time values Called as f( const_v<42>{}); void f( const_v< v>) { � if constexpr ( v == 42 ) Advantages § { /*. . . */ } } § void f( const_v< 0>) { /*. . . */ } Supports specialization where true specialization does not exists (functions, member class templates) Tags may be passed through forwarders which cannot pass explicit template arguments k. emplace_back( const_v<42>{});

Employing type non-equivalence template< class T, T v > � std: : integral_constant<T, v>

Employing type non-equivalence template< class T, T v > � std: : integral_constant<T, v> struct integral_constant; § auto f = [](auto && a){ § if constexpr ( a == 42 ) { /*. . . */ } f(a); // allows specialization }; instantiable class containing no data allows conversion to T which returns v

Policy classes NPRG 041 Programming in C++ - 2016/2017 David Bednárek 25

Policy classes NPRG 041 Programming in C++ - 2016/2017 David Bednárek 25

Policy classes � Policy class works as a set of parameters for generic code

Policy classes � Policy class works as a set of parameters for generic code Types (defined by typedef/using or nested classes/enums) � Constants (defined by static constexpr) � Functions (defined as static) � � The use of policy class instead of individual arguments. . . makes template names shorter �. . . avoids order-related mistakes � This is the only way how functions may become parameters of a template � � Policy classes are never instantiated � This allows additional tricks which would not work well in instantiated classes NPRG 041 Programming in C++ - 2016/2017 David Bednárek 26

Mixin � Mixin Idea: Mixin is a prefabricated set of definitions that is dumped

Mixin � Mixin Idea: Mixin is a prefabricated set of definitions that is dumped into a scope � Example: Every random-access iterator must define 5 types and 20 operators, providing similar functionality in various ways. It would be advantageous to have syntax like this: � class my_iterator includes generic_random_access_iterator</* some arguments */> { // some definitions }; § � There is no such syntax in C++ yet (and will not be in foreseeable future) Important: Functions inside the mixin must be able to see the other definitions in the scope where the mixin is used, as if they were located there mixin 1 { void function 1() { ++var 1; } }; mixin 2 { void function 2() { function 1(); } }; class final_class includes mixin 1, mixin 2 { int var 1; }; NPRG 041 Programming in C++ - 2016/2017 David Bednárek 27

Mixin � Emulating mixins by inheritance: class my_iterator : public generic_random_access_iterator</* some arguments */>

Mixin � Emulating mixins by inheritance: class my_iterator : public generic_random_access_iterator</* some arguments */> { // some definitions }; � Problems § A part of the required interface references the final class: my_iterator & operator+=( std: : ptrdiff_t b) { /*. . . */ return *this; } § § How can we access my_iterator inside generic_random_access_iterator? The required interface contains non-member functions: my_iterator operator+( std: : ptrdiff_t a, const my_iterator & b); § § How can we implement this inside the mixin? The requirements include a conversion between related iterators: § Either via conversion constructor in my_const_iterator – but constructors are not inherited my_const_iterator(const my_iterator & b); § Or via conversion operator in my_iterator operator my_const_iterator() const; § This is an additional method present in only one of the two iterator classes NPRG 041 Programming in C++ - 2016/2017 David Bednárek 28

Mixin � Referencing the final class in a mixin § The mixin must have

Mixin � Referencing the final class in a mixin § The mixin must have a parameter template< typename final_class, /*. . . */> class generic_random_access_iterator { final_class & operator+=( std: : ptrdiff_t b) { /*. . . */ return *static_cast<final_class*>(this); } }; § The mixin is used like this: class my_iterator : public generic_random_access_iterator< my_iterator, /*. . . */> { /*. . . */ }; � This approach is ugly and dangerous: class my_second_iterator : public generic_random_access_iterator< my_iterator, /*. . . */> { /*. . . */ }; NPRG 041 Programming in C++ - 2016/2017 David Bednárek 29

Mixin � Global functions as a mixin template< typename final_class, /*. . . */>

Mixin � Global functions as a mixin template< typename final_class, /*. . . */> final_class operator+( std: : ptrdiff_t a, const generic_random_access_iterator<final_class, /*. . . */> & b) { /*. . . */ } § This is an operator on the mixin class, not on the final_class § � It could have unwanted effects The conversion operator § We need to know the other final class too template< typename final_class, typename final_const_class, /*. . . */> class generic_random_access_iterator { operator final_const_class() const { /*. . . */ } }; § But we don’t want the conversion the other way – we need two mixin classes! NPRG 041 Programming in C++ - 2016/2017 David Bednárek 30

Mixins and policy classes � Instead of writing this. . . template< typename final_class,

Mixins and policy classes � Instead of writing this. . . template< typename final_class, typename final_const_class, /*. . . */> class generic_random_access_iterator { operator final_const_class() const { /*. . . */ } }; � . . . policy classes allow shorter template parameter lists. . . template< typename policy> class generic_random_access_iterator { operator typename policy: : final_const_class () const { /*. . . */ } }; § . . . at the cost of declaring a policy class my_iterator; class my_const_iterator; struct my_policy { using final_class = my_iterator; using final_const_iterator = my_const_iterator; /*. . . */ }; class my_iterator : public generic_random_access_iterator< my_policy> { /*. . . */ }; NPRG 041 Programming in C++ - 2016/2017 David Bednárek 31

Mixins and policy classes § With a policy class, things may be also implemented

Mixins and policy classes § With a policy class, things may be also implemented the other way round: struct my_const_policy { using const_policy = my_const_policy; /*. . . */ }; struct my_policy { using const_policy = my_const_policy; /*. . . */ }; using my_iterator = generic_random_access_iterator< my_policy>; using my_const_iterator = generic_random_access_iterator< my_const_policy>; § This is probably the only approach which can really work in C++ § § It is limited to one final generic class, not really a mixin It may correctly support non-member functions like: template< typename P> inline generic_random_access_iterator< P> operator+( std: : ptrdiff_t a, generic_random_access_iterator< P> b); NPRG 041 Programming in C++ - 2016/2017 David Bednárek 32