std variant std variant std variant a polymorphic

  • Slides: 16
Download presentation
std: : variant

std: : variant

std: : variant • std: : variant - a polymorphic type containing one of

std: : variant • std: : variant - a polymorphic type containing one of a fixed set of types • Safe replacement of C unions • plus a data element to remember which type it is using VT = std: : variant< T 0, T 1, T 2>; • More space-effective than a pointer to a dynamically-allocated object with inheritance • If the types are of similar sizes • But not extensible • There is no common interface (base class) required in the types • Allows assignment from any of the types T 0 v 0 = /*. . . */; VT a = v 0; VT b(std: : in_place_type<T 1>, /*. . . */); // VT c(std: : in_place_index<2>, /*. . . */); // b = v 0; // calls T 1: : ~T 1(), T 0: : T 0(v 0) c. emplace<T 1>(/*. . . */); // calls T 2: : ~T 2(), a. emplace<2>(/*. . . */); // calls T 0: : ~T 0(), calls T 1: : T 1(/*. . . */) calls T 2: : T 2(/*. . . */) T 1: : T 1(/*. . . */) T 2: : T 2(/*. . . */) • Access to the contained data: void action( VT & vo) { switch ( vo. index() case 0: { T 0 & v 0 = case 1: { T 1 & v 1 = case 2: { T 2 & v 2 = } ) { std: : get< 0>(vo); v 0. f(); } break; std: : get< 1>(vo); v 1. g(); } break; std: : get< 2>(vo); v 2. h(); } break; 2 NPRG 041 Programová ní v C++ 2019/2020

Storing polymorphic objects Using inheritance T 1 Base useful data Using std: : variant

Storing polymorphic objects Using inheritance T 1 Base useful data Using std: : variant index T 1 useful data T 2 T 3 type info std: : unique_ptr<Base> x = std: : make_unique<T 1>(/*. . . */); • Higher run-time cost • Pointer, dynamic allocation • Extensible set of concrete types std: : variant<T 1, T 2, T 3> x = T 1(/*. . . */); • Lower run-time cost • No dynamic allocation • Always requires maximum space • Not intrusive • Does not use any base class • Fixed set of alternative types 3 NPRG 041 Programmin g in C++ 2019/2020

std: : variant and static visitor using VT = std: : variant< T 0,

std: : variant and static visitor using VT = std: : variant< T 0, T 1, T 2>; • std: : visit - Usage through a polymorphic functor struct Visitor. A { void operator()( T 0 & x) { /*. . . */ } void operator()( T 1 & x) { /*. . . */ } void operator()( T 2 & x) { /*. . . */ } }; void action( VT & vo) { Visitor. A va; std: : visit(va, vo); } • It may be used with a polymorphic lambda std: : visit([](auto && a){ a. something(); }, vo); • All the types need a common interface • The interface is not explicitly declared • This is a static equivalent of inheritance and virtual functions • At higher cost – visit requires a tricky implementation similar to visitors • But the memory footprint may still be smaller – no dynamic allocation 4 NPRG 041 Programová ní v C++ 2019/2020

Conversions

Conversions

Special member functions • Conversion constructors class T { T( U x); }; •

Special member functions • Conversion constructors class T { T( U x); }; • Generalized copy constructor • Defines conversion from U to T • If conversion effect is not desired, all one-argument constructors must be "explicit": explicit T( U v); • Conversion operators class T { operator U() const; }; • Defines conversion from T to U • Returns U by value (using copy-constructor of U, if U is a class) • U may be a reference like V& if life-time considerations allow • Compilers will never use more than one user-defined conversion in a chain • The user-defined conversion may be combined with several built-in conversions

Type cast • Various syntax styles (T)e • C-style cast • Inherited from C

Type cast • Various syntax styles (T)e • C-style cast • Inherited from C T(e) • Function-style cast • Equivalent to (T)e • T must be single type identifier or single keyword • Type conversion operators • Differentiated by intent (strength and associated danger) of cast: const_cast<T>(e) static_cast<T>(e) reinterpret_cast<T>(e) • New - run-time assisted cast: dynamic_cast<T>(e)

Const cast const_cast<T>(e) • Suppressing const flags of pointers/references • const U & =>

Const cast const_cast<T>(e) • Suppressing const flags of pointers/references • const U & => U & • const U * => U * • It allows violation of const-ness • In most cases, mutable is a better solution • Example: Counting references to a logically constant object class Data { public: void register_pointer() const { references++; } private: /*. . . data. . . */ mutable int references; };

Static cast static_cast<T>(e) • All implicit conversions • Explicit cast used to enforce the

Static cast static_cast<T>(e) • All implicit conversions • Explicit cast used to enforce the conversion in ambiguous situations • • • Loss-less and lossy number conversions (e. g. int <=> double) Adding const/volatile modifiers to pointers/references Pointer to void* Derived-to-base pointer/reference conversions Invoke any constructor of T capable to accept e • Including copy/move-constructors and explicit constructors • Invoke a conversion operator T() • Some explicit conversions • Anything to void, i. e. discarding the value (e. g. in a conditional expression) • Base-to-derived pointer/reference conversions • No runtime checks, it may produce invalid pointers – use dynamic_cast to check • Integer to an enumeration • May produce undefined results if not mappable • void* to any pointer • No runtime checks possible (even if the object contain type information)

Dynamic cast dynamic_cast<T>(e) • Base-to-derived pointer/reference conversions • Runtime checks included – requires type

Dynamic cast dynamic_cast<T>(e) • Base-to-derived pointer/reference conversions • Runtime checks included – requires type information in the e object • At least one virtual function required in the type of e • If the dynamic type of e is not T (or derived from T). . . • Pointers: Returns nullptr • References: Throws std: : bad_cast class Base { public: virtual ~Base(); /* base class must have at least one virtual function */ }; class X : public Base { /*. . . */ }; class Y : public Base { /*. . . */ }; Base * p = /*. . . */; X * xp = dynamic_cast< X *>( p); if ( xp ) { /*. . . */ } Y * yp = dynamic_cast< Y *>( p); if ( yp ) { /*. . . */ }

Reinterpret cast reinterpret_cast<T>(e) • Implementation-dependent conversions • • Pointer to integer Integer to pointer

Reinterpret cast reinterpret_cast<T>(e) • Implementation-dependent conversions • • Pointer to integer Integer to pointer Any function-pointer to any function-pointer Any data-pointer to any other data-pointer • No address correction even if pointers are related by inheritance • Any reference to any other reference • Mostly used to read/write binary files/packets/. . . void put_double( std: : ostream & o, const double & d) { o. write( reinterpret_cast< char *>( & d), sizeof( double)); } • The file contents is implementation-dependent – not portable

Templates

Templates

Templates • A template is a generic declaration with compile-time formal arguments of these

Templates • A template is a generic declaration with compile-time formal arguments of these kinds: • any type template< typename T> // also template< class T> • [C++20] the type may be constrained using Concepts • template< • a list of any types (in a variadic template) typename. . . TL> integral number (the actual argument must be a compile-time constant) std: : size_t N> a list of integral numbers (in a variadic template) int. . . NL> another class template (with the given template argument list) template< typename, std: : size_t> class T> a pointer (the actual argument must be an address of a static variable/function) • almost never used, functors work better template< const char * p, inf (*fp)(int, int)> • A function/lambda with auto arguments is also a template

Templates • Class templates • Global classes • Classes nested in other classes, including

Templates • Class templates • Global classes • Classes nested in other classes, including other class template< typename T, std: : size_t N> class array { /*. . . */ }; // usage: array<double, 3> • Function templates • Global functions • Member functions, including constructors template< typename T> inline T max( T x, T y) { /*. . . */ } // usage: max(p, q) or max<int>(p, q) • Type alias templates [C++11] template< typename T> using array 3 = std: : array< T, 3>; // usage: array 3<double> • Static variable templates [C++14] • Mostly used as global “constants” acting as compile-time functions on types template< typename T> inline constexpr std: : size_t my_sizeof = sizeof(T); // usage: my_sizeof<double>

Templates • Template instantiation • Using the template with particular type and constant parameters

Templates • Template instantiation • Using the template with particular type and constant parameters • Class, type alias, variable templates: parameters always specified explicitly using my_type = std: : array< int, 10>; using signed_size_t = std: : make_signed_t< std: : size_t>; // std: : ptrdiff_t constexpr bool char_is_signed = std: : is_signed_v<char>; • Function templates: parameters specified explicitly or implicitly • Implicitly - derived by compiler from the types of value arguments int a, b, c; a = max( b, c); // calls max< int> • Explicitly a = max< double>( b, 3. 14); • Mixed: Some (initial) arguments explicitly, the rest implicitly std: : tuple< int, double, int, char, unsigned> v; x = get< 3>( v); // calls std: : get< 3, std: : tuple< int, double, int, char, unsigned>> • Note: Argument-dependent lookup uses both the type arguments in <> and the types of arguments in ()

Templates • Multiple templates with the same name • Class templates: • one "master"

Templates • Multiple templates with the same name • Class templates: • one "master" template< typename T> class vector {/*. . . */}; • any number of specializations which override the master template • partial specialization template< typename T, std: : size_t n> class unique_ptr< T [n]> {/*. . . */}; • explicit specialization template<> class vector< bool> {/*. . . */}; • Function templates: • • any number of templates with the same name shared with non-templated functions resolved via “argument-dependent-lookup” and “overload resolution” no explicit/partial specialization possible • Instead, the tag-class trick is often used: std: : sort(std: : execution: : par, k. begin(), k. end()); • std: : execution: : par is an (empty) global variable of type std: : execution: : parallel_policy • the type enforces the use of a different (parallel) implementation of std: : sort