Download presentation
Presentation is loading. Please wait.
1
Advanced Programming in C++
NPRG041 Programming in C /2017 David Bednárek
2
Organization 2/2 Z/Zk Lecture "Seminars" Credit Exam
mixture of lecturers "Seminars" only 5 sessions during the semester dedicated to 3 homework assignments before: assignment, explanation, hints after: evaluation, frequent errors, best solutions Credit at least 15 points from assignments required before exam Exam homeworks: pts ⇉ 1, pts ⇉ 2, pts ⇉ 3 if you are happy with this result, you may skip the oral part oral exam ⇉ ±10pts beware: it may improve or worsen your result
3
Homework assignments and seminars
week 6 March 26 Seminar: Assignment #1 week 8 April 9 Seminar: Assignment #2 April 11: Deadline #1 week 10 April 23 Seminar: Assignment #3, Evaluation #1 April 24: Deadline #2 week 12 May 7 Seminar: Evaluation #2 May 8: Deadline #3 week 14 May 21 Seminar: Evaluation #3 Deadlines: Wednesdays at 23:59 local time submit the required files to ReCodeX late submissions = 1 point per each day deduced
4
Books C++11/14/17/20 Scott Meyers: Effective Modern C++ (C++11/C++14)
en.cppreference.com/w/cpp Scott Meyers: Effective Modern C++ (C++11/C++14) O'Reilly 2014 Methodology of programming Scott Meyers: Overview of the New C++ (C++11) Collected slides Motivation behind C++11 explained Bjarne Stroustrup: The C++ Programming Language - 4th Edition Addison-Wesley 2013 Complete C++11 textbook Stanley B. Lippman, Josée Lajoie, Barbara E. Moo: C++ Primer (5th Edition) Addison-Wesley 2012
5
Older books Intermediate – before C++11 = outdated but may be worth reading Andrei Alexandrescu, Herb Sutter: C++ Coding Standards (2005) Scott Meyers: Effective C++ (1998) More Effective C++ (1996) Effective STL (2001) Herb Sutter: Exceptional C++ (2000) More Exceptional C++ (2002) Exceptional C++ Style (2004) Nicolai M. Josuttis: Object-Oriented Programming in C++ (2002) The C++ Standard Library (1999)
6
Exception handling
7
Why exceptions? Returning error codes Run-time cost
error_code f() { auto rc1 = g1(); if ( rc1.bad() ) return rc1; auto rc2 = g2(); if ( rc2.bad() ) return rc2; return g3(); } Run-time cost small if everything is OK small if something wrong Throwing exceptions void f() { g1(); g2(); g3(); } Run-time cost none if everything is OK big if something wrong
8
Exception handling Exceptions are "jumps" Start: throw statement
Destination: try-catch block Determined in run-time The jump may exit a procedure Local variables will be properly destructed by destructors Besides jumping, a value is passed The type of the value determines the destination Typically, special-purpose classes Catch-block matching can understand inheritance class AnyException { /*...*/ }; class WrongException : public AnyException { /*...*/ }; class BadException void f() { if ( something == wrong ) throw WrongException( something); if ( anything != good ) throw BadException( anything); } void g() try { f(); catch ( const AnyException & e1 ) { /*...*/
9
Exception handling Exceptions are "jumps" Start: throw statement
Destination: try-catch block Determined in run-time The jump may exit a procedure Local variables will be properly destructed by destructors Besides jumping, a value is passed The type of the value determines the destination Typically, special-purpose classes Catch-block matching can understand inheritance The value may be ignored class AnyException { /*...*/ }; class WrongException : public AnyException { /*...*/ }; class BadException void f() { if ( something == wrong ) throw WrongException(); if ( anything != good ) throw BadException(); } void g() try { f(); catch ( const AnyException &) { /*...*/
10
Exception handling Exceptions are "jumps" Start: throw statement
Destination: try-catch block Determined in run-time The jump may exit a procedure Local variables will be properly destructed by destructors Besides jumping, a value is passed The type of the value determines the destination Typically, special-purpose classes Catch-block matching can understand inheritance The value may be ignored There is an universal catch block class AnyException { /*...*/ }; class WrongException : public AnyException { /*...*/ }; class BadException void f() { if ( something == wrong ) throw WrongException(); if ( anything != good ) throw BadException(); } void g() try { f(); catch (...) { /*...*/
11
Exception handling Exception handling
Evaluating the expression in the throw statement The value is stored "somewhere" Stack-unwinding Blocks and functions are being exited Local and temporary variables are destructed by calling destructors (user code!) Stack-unwinding stops in the try-block whose catch-block matches the throw expression type catch-block execution The throw value is still stored may be accessed via the catch-block argument (typically, by reference) "throw;" statement, if present, continues stack-unwinding Exception handling ends when the accepting catch-block is exited normally Also using return, break, continue, goto Or by invoking another exception
12
C++11 Exception handling Materialized exceptions
std::exception_ptr is a smart- pointer to an exception object Uses reference-counting to deallocate std::current_exception() Returns (the pointer to) the exception being currently handled The exception handling may then be ended by exiting the catch-block std::rethrow_exception( p) (Re-)Executes the stored exception like a throw statement This mechanism allows: Propagating the exception to a different thread Signalling exceptions in the promise/future mechanism std::exception_ptr p; void g() { try { f(); } catch (...) { p = std::current_exception(); void h() std::rethrow_exception( p); C++11
13
Exception handling Out-of-memory, network errors, end-of-file, ...
Throwing and handling exceptions is slower than normal execution Compilers favor normal execution at the expense of exception-handling complexity Use exceptions only for rare events Out-of-memory, network errors, end-of-file, ... Mark procedures which cannot throw by noexcept void f() noexcept { /*...*/ } it may make code calling them easier (for you and for the compiler) noexcept may be conditional template< typename T> void g( T & y) noexcept( std::is_nothrow_copy_constructible< T>::value) { T x = y;
14
Exception handling Mark procedures which cannot throw by noexcept
Example: Resizing std::vector<T> When inserting above capacity, the contents must be relocated to a larger memory block Before C++11, the relocation was done by copying, i.e. calling T(const T &) If a copy constructor threw, the new copies were discarded and the insert call reported failure by throwing Thus, if the insert threw, no observable change happened Note: Correct destruction of copies is possible only if the destructor is not throwing: ~T() noexcept In C++11, the relocation shall be done by moving If a move constructor throws, the previously moved elements shall be moved back, but it can throw again! The relocation is done by moving only if the move constructor is declared as T(T &&) noexcept ... or if it is declared implicitly and all elements satisfy the same property Otherwise, the slower copy method is used!
15
Exception handling Standard exceptions <stdexcept>
All standard exceptions are derived from class exception the member function what() returns the error message bad_alloc: not-enough memory bad_cast: dynamic_cast on references Derived from logic_error: domain_error, invalid_argument, length_error, out_of_range e.g., thrown by vector::at Derived from runtime_error: range_error, overflow_error, underflow_error Hard errors (invalid memory access, division by zero, ...) are NOT signalized as exceptions These errors might occur almost anywhere The need to correctly recover via exception handling would prohibit many code optimizations Nevertheless, there are (proposed) changes in the language specification that will allow reporting hard errors by exceptions at reasonable cost
16
Exception-safe programming
Bezpečné programování s výjimkami
17
Exception-safe programming
Using throw a catch is simple Producing code that works correctly in the presence of exceptions is hard Exception-safety Exception-safe programming void f() { int * a = new int[ 100]; int * b = new int[ 200]; g( a, b); delete[] b; delete[] a; } If new int[ 200] throws, the int[100] block becomes inaccessible If g() throws, two blocks become inaccessible
18
Exception-safe programming
void f() { int * a = new int[ 100]; int * b = new int[ 200]; g( a, b); delete[] b; delete[] a; } If new int[ 200] throws, the int[100] block becomes inaccessible If g() throws, two blocks become inaccessible Safety is expensive void f() { int * a = new int[ 100]; try { int * b = new int[ 200]; g( a, b); } catch (...) { delete[] b; throw; } delete[] b; delete[] a; throw; delete[] a;
19
Exception-safe programming
void f() { int * a = new int[ 100]; int * b = new int[ 200]; g( a, b); delete[] b; delete[] a; } If new int[ 200] throws, the int[100] block becomes inaccessible If g() throws, two blocks become inaccessible Smart pointers can help void f() { auto a = std::make_unique<int[]>(100); auto b = std::make_unique<int[]>(200); g( &*a, &*b); } Exception processing correctly invokes the destructors of smart pointers
20
Exception-safe programming
There are more problems besides memory leaks std::mutex my_mutex; void f() { my_mutex.lock(); // do something critical here my_mutex.unlock(); // something not critical } If something throws in the critical section, this code will leave the mutex locked forever! RAII: Resource Acquisition Is Initialization Constructor grabs resources Destructor releases resources Also in the case of exception std::mutex my_mutex; void f() { std::lock_guard< std::mutex> lock( my_mutex); // do something critical here } // something not critical There is a local variable “lock” that is never (visibly) used beyond its declaration! Nested blocks matter!
21
Exception-safe programming
An incorrectly implemented copy assignment T & operator=( const T & b) { if ( this != & b ) delete body_; body_ = new TBody( b.length()); copy( * body_, * b.body_); } return * this; Produces invalid object when TBody constructor throws Does not work when this==&b Exception-safe implementation T & operator=( const T & b) { T tmp(b); swap(tmp); return * this; } void swap( T & b) std::swap( body_, b.body_); Can reuse code already implemented in the copy constructor and the destructor Does work when this==&b
22
Exception-safe programming
Language-enforced rules Destructors may not end by throwing an exception Constructors of static variables may not end by throwing an exception Copying of exception objects may not throw Compilers sometimes generate implicit try-catch blocks When constructing a compound object, a constructor of an element may throw Array allocation Class constructors The implicit catch block destructs previously constructed parts and rethrows
23
Exception-safe programming
Theory (Weak) exception safety Exceptions does not cause inconsistent state No memory leaks No invalid pointers Application invariants hold ...? Strong exception safety Exiting function by throwing means no change in (observable) state Observable state = public interface behavior Also called "Commit-or-rollback semantics"
24
Strong exception safety
void f() { g1(); g2(); } When g2() throws... f() shall signal failure (by throwing) failure shall imply no change in state but g1() already changed something it must be undone void f() { g1(); try { g2(); } catch(...) { undo_g1(); throw; } NPRG041 Programming in C /2017 David Bednárek
25
Strong exception safety
Undoing is sometimes impossible e.g. erase(...) Code becomes unreadable Easy to forgot the undo Observations If a function does not change observable state, undo is not required The last function in the sequence is never undone void f() { g1(); try { g2(); g3(); } catch(...) { undo_g2(); throw; } undo_g1(); NPRG041 Programming in C /2017 David Bednárek
26
Strong exception safety
Check-and-do style Check if everything is correct Then do everything These functions must not throw Still easy to forget a check Work is often duplicated It may be difficult to write non- throwing do-functions void f() { check_g1(); check_g2(); check_g3(); do_g1(); do_g2(); do_g3(); } NPRG041 Programming in C /2017 David Bednárek
27
Strong exception safety
Check-and-do with tokens Each do-function requires a token generated by the check-function Checks can not be omitted Tokens may carry useful data Duplicate work avoided It may be difficult to write non- throwing do-functions void f() { auto t1 = check_g1(); auto t2 = check_g2(); auto t3 = check_g3(); do_g1( t1); // or t1.doit(); do_g2( t2); do_g3( t3); } NPRG041 Programming in C /2017 David Bednárek
28
Strong exception safety
Prepare-and-commit style Prepare-functions generate a token Tokens must be committed to produce observable change Commit-functions must not throw If not committed, destruction of tokens invokes undo If some of the commits are forgotten, part of the work will be undone void f() { auto t1 = prepare_g1(); auto t2 = prepare_g2(); auto t3 = prepare_g3(); commit_g1( t1); // or t1.commit(); commit_g2( t2); commit_g3( t3); } NPRG041 Programming in C /2017 David Bednárek
29
Strong exception safety
Two implementations: Do-Undo Prepare-functions make observable changes and return undo-plans Commit-functions clear undo-plans Token destructors apply undo-plans Prepare-Commit Prepare-functions return do-plans Commit-functions perform do- plans Token destructors clear do-plans Commits and destructors must not throw Unsuitable for inserting Use Do-Undo when inserting Destructor does erase Use Prepare-Commit when erasing Commit does erase void f() { auto t1 = prepare_g1(); auto t2 = prepare_g2(); auto t3 = prepare_g3(); commit_g1( t1); // or t1.commit(); commit_g2( t2); commit_g3( t3); } NPRG041 Programming in C /2017 David Bednárek
30
Strong exception safety
Problems: Some commits may be forgotten Do-Undo style produces temporarily observable changes Unsuitable for parallelism Atomic commit required Prepare-functions concatenate do- plans Commit executes all do-plans "atomically" It may be wrapped in a lock_guard Commit may throw! It is the only function with observable effects Inside commit Do all inserts If some fails, previous must be undone Do all erases Erases do not throw (usually) Chained style void f() { auto t1 = prepare_g1(); auto t2 = prepare_g2( std::move(t1)); auto t3 = prepare_g3( std::move(t2)); t3.commit(); } Symbolic style auto t2 = std::move(t1) | prepare_g2(); auto t3 = std::move(t2) | prepare_g3(); NPRG041 Programming in C /2017 David Bednárek
31
Templates
32
Templates Template Class templates Function templates
a generic piece of code parameterized by types, class templates, and integer constants Class templates Global classes Classes nested in other classes, including class templates template< typename T, std::size_t N> class array { /*...*/ }; Function templates Global functions Member functions, including constructors template< typename T> inline T max( T x, T y) { /*...*/ } Type templates [C++11] using array3 = std::array< T, 3>; Variable templates [C++14] Global variables and static data members factory_class<T> factory;
33
Templates Template instantiation
Using the template with particular type and constant parameters Class, type, and variable templates: parameters specified explicitly std::array< int, 10> x; 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::array< int, 5> v; x = std::get< 3>( v); // calls std::get< 3, std::array< int, 5>>
34
Templates and compilation
Compiles [may] check template code when defined Without the knowledge of template arguments Syntactic hints from the author may be required Compilers generate code only when templates are instantiated Different instantiations do not share code It sometimes causes unwanted code size explosion There is no run-time support required for templates Code generated for templates is identical to non-templated equivalents There is no performance penalty for generic code Except that generic programming may encourage programmers to be lazy
35
Writing templates Compiler needs hints from the programmer
Dependent names have unknown meaning/contents template< typename T> class X { type names must be explicitly designated using U = typename T::B; typename U::D p; // U is also a dependent name using Q = typename Y<T>::C; void f() { T::D(); } // T::D is not a type explicit template instantiations must be explicitly designated bool g() { return 0 < T::template h<int>(); } int j() { return p.template k<3>(); } // type of p is a dependent name } members inherited from dependent classes must be explicitly designated template< typename T> class X : public T const int K = T::B + 1; // B is not directly visible although inherited void f() { return this->a; } // a is not directly visible
36
Templates and compilation
Implicit template instantiation When a class template specialization is referenced in context that requires a complete object type, or… … when a function template specialization is referenced in context that requires a function definition to exist… … the template is instantiated (the code for it is actually compiled) unless the template was already explicitly specialized or explicitly instantiated at link time, identical instantiations generated by different translation units are merged Instantiation of template member functions Instantiation of a class template doesn't instantiate any of its member functions unless they are also used The definition of a template must be visible at the point of implicit instantiation
37
Templates and compilation
The definition of a template must be visible at its instantiation Classes and types The visibility of definition is required also in the non-template case Most class and type definitions must reside in header files Function templates (including non-template member functions of class templates etc.) The template visibility rule is equivalent to rules for inline functions Non-inline function templates are almost unusable Most function template definitions must reside in header files (and be inline) If only a declaration is visible, the compiler will not complain - it will generate a call but not the code of the function called – the linker will complain In rare cases, the visibility rule may be silenced by explicit instantiation: Applicable only if all required argument combinations may be enumerated template int max<int>(int, int); // forces instantiation of max<int> template double max(double, double); // forces instantiation of max<double> template class X<int>; // forces instantiation of all member functions of X<int>
38
Declarations and definitions
39
Declarations and definitions
A construct to declare the existence (of a class/variable/function/...) Identifier Some basic properties Ensures that (some) references to the identifier may be compiled Some references may require definition Definition A construct to completely define (a class/variable/function/...) Class contents, variable initialization, function implementation Ensures that the compiler may generate runtime representation Every definition is a declaration Declarations allow (limited) use of identifiers without definition Independent compilation of modules Solving cyclic dependences Minimizing the amount of code that requires (re-)compilation
40
Declarations and definitions
One-definition rule #1: One translation unit... (module, i.e. one .cpp file and the .hpp files included from it) ... may contain at most one definition of any item One-definition rule #2: Program... (i.e. the .exe file including the linked .dll files) ... may contain at most one definition of a variable or a non-inline function Definitions of classes, types or inline functions may be contained more than once (due to inclusion of the same .hpp file in different modules) If these definitions are not identical, undefined behavior will occur Beware of version mismatch between headers and libraries Diagnostics is usually poor (by linker)
41
Cyclic references in code
Class A refers to B struct A { A( int p) : v{p} {} B generate_b() { return B(this); } int v; Declaration of generate_b requires declaration of B Therefore definition of A requires declaration of B Definition of generate_b requires definition of B Class B refers to A struct B { B( A * q) : link{q} {} int get_v() { return link->v; } A * link; Declarations of constructor and link require declaration of A Therefore definition of B requires declaration of A Definition of get_v requires definition of A
42
Cyclic references in code
struct B; struct A { A( int p) : v{p} {} B generate_b(); int v; } struct B { B( A * q) : link{q} {} int get_v() { return link->v; A * link; inline B A::generate_b() return B(this); A correct ordering There are more possible Declaration of B Definition of A Except definition of generate_b Definition of B Definition of generate_b
43
Cyclic references between headers
A.hpp #ifndef A_hpp_ #define A_hpp_ #include “B.hpp” struct A { A( int p) : v{p} {} B generate_b(); int v; } inline B A::generate_b() { return B(this); #endif WRONG! When A.hpp is compiled, ifndef guards prohibit recursive A.hpp inclusion from B.hpp Definition of B will not see the definition of A B.hpp #ifndef B_hpp_ #define B_hpp_ #include “A.hpp” struct B { B( A * q) : link{q} {} int get_v() { return link->v; } A * link; #endif WRONG! When B.hpp is compiled, ifndef guards prohibit recursive B.hpp inclusion from A.hpp Definition of A will not see the declaration of B
44
Cyclic references between headers
A.hpp #ifndef A_hpp_ #define A_hpp_ struct B; struct A { A( int p) : v{p} {} B generate_b(); int v; } #include “B.hpp” inline B A::generate_b() { return B(this); #endif A ticket to a madhouse Never bury include directives inside header or source files B.hpp #ifndef B_hpp_ #define B_hpp_ #include “A.hpp” struct B { B( A * q) : link{q} {} int get_v() { return link->v; } A * link; #endif STILL WRONG! B.hpp includes TOO MUCH A.hpp contains the definition of generate_b which cannot compile before definitionof B
45
Cyclic references between headers
A.hpp #ifndef A_defined #define A_defined struct B; struct A { A( int p) : v{p} {} B generate_b(); int v; } #endif #ifndef generate_b_defined #define generate_b_defined #include “B.hpp” inline B A::generate_b() { return B(this); B.hpp #include “A.hpp” #ifndef B_hpp_ #define B_hpp_ struct B { B( A * q) : link{q} {} int get_v() { return link->v; } A * link; #endif It works, but your colleagues will want to kill you Never use define guards for anything else than complete header files including the include directives
46
Cyclic references between classes – solved with .cpp files
A.hpp #ifndef A_hpp_ #define A_hpp_ struct B; struct A { A( int p) : v{p} {} B generate_b(); int v; } #endif A.cpp #include “A.hpp” #include “B.hpp” inline B A::generate_b() { return B(this); B.hpp #ifndef B_hpp_ #define B_hpp_ #include “A.hpp” struct B { B( A * q) : link{q} {} int get_v() { return link->v; } A * link; #endif Still problematic Including A.hpp enable you to call generate_b which returns undefined class B
47
Cyclic references between classes – solved with more .hpp files
Atypes.hpp #ifndef Atypes_hpp_ #define Atypes_hpp_ struct B; struct A { A( int p) : v{p} {} B generate_b(); int v; } #endif A.hpp #ifndef A_hpp_ #define A_hpp_ #include “Atypes.hpp” #include “B.hpp” inline B A::generate_b() { return B(this); Never include Atypes.hpp or Btypes.hpp directly Except in the corresponding A.hpp and B.hpp Btypes.hpp #ifndef Btypes_hpp_ #define Btypes_hpp_ struct A; struct B { B( A * q) : link{q} {} int get_v() { return link->v; } A * link; #endif B.hpp #ifndef B_hpp_ #define B_hpp_ #include “Btypes.hpp” #include “A.hpp” inline int B::get_v() { Instruct everybody to include A.hpp or B.hpp Otherwise they may miss some inline definition Which results in linker error if called
48
Cyclic references between templates – solved with more .hpp files
Atypes.hpp #ifndef Atypes_hpp_ #define Atypes_hpp_ template< typename T> struct B; template< typename T> struct A { A( T p) : v{p} {} B<T> generate_b(); T v; } #endif A.hpp #ifndef A_hpp_ #define A_hpp_ #include “Atypes.hpp” #include “B.hpp” template< typename T> inline B<T> A<T>::generate_b() { return B<T>(this); Never include Atypes.hpp or Btypes.hpp directly Except in the corresponding A.hpp and B.hpp Btypes.hpp #ifndef Btypes_hpp_ #define Btypes_hpp_ template< typename T> struct A; template< typename T> struct B { B( A<T> * q) : link{q} {} T get_v() { return link->v; } A<T> * link; #endif B.hpp #ifndef B_hpp_ #define B_hpp_ #include “Btypes.hpp” #include “A.hpp” template< typename T> inline T B<T>::get_v() { Instruct everybody to include A.hpp or B.hpp Otherwise they may miss some inline definition Which results in linker error if called
49
Variadic templates
50
C++11 Variadic templates Template heading
Allows variable number of type arguments template< typename ... TList> class C { /* ... */ }; typename ... declares named template parameter pack may be combined with regular type/constant arguments template< typename T1, int c2, typename ... TList> class D { /* ... */ }; also in partial template specializations template< typename T1, typename ... TList> class C< T1, TList ...> { /* ... */ }; C++11
51
C++11 Variadic templates template parameter pack - a list of types
template< typename ... TList> template parameter pack - a list of types may be referenced inside the template: always using the suffix ... as type arguments to another template: X< TList ...> Y< int, TList ..., double> in argument list of a function declaration: void f( TList ... plist); double g( int a, double c, TList ... b); this creates a named function parameter pack in several less frequent cases, including base-class list: class E : public TList ... number of elements in the parameter pack: sizeof...(TList) C++11
52
C++11 Variadic templates named function parameter pack
template< typename ... TList> void f( TList ... plist); named function parameter pack may be referenced inside the function: always using the suffix ... as parameters in a function call or object creation: g( plist ...) new T( a, plist ..., 7) T v( b, plist ..., 8); constructor initialization section (when variadic base-class list is used) E( TList ... plist) : TList( plist) ... { } other infrequent cases C++11
53
C++11 Variadic templates
template< typename ... TList> void f( TList ... plist); parameter packs may be wrapped into a type construction/expression the suffix ... works as compile-time "for_each" parameter pack name denotes the place where every member will be placed more than one pack name may be used inside the same ... (same length required) the result is a list of types (in a template instantiation or a function parameter pack declaration) X< std::pair< int, TList *> ...> class E : public U< TList> ... void f( const TList & ... plist); a list of expressions (in a function call or object initialization) g( make_pair( 1, & plist) ...); h( static_cast< TList *>( plist) ...); // two pack names in one ... m( sizeof( TList) ...); // different from sizeof...( TList) other infrequent cases C++11
54
fold expressions - variadic templates
C++14 – recursion C simplification auto old_sum(){ return 0; } template<typename T1, typename... T> auto old_sum(T1 s, T... ts){ return s + old_sum(ts...); } template<typename... T> ????( T... pack) ( pack op ... ) // P1 op (P2 op ... (Pn-1 op Pn)) ( ... op pack ) // ((P1 op P2) op ... Pn-1) op Pn ( pack op ... op init ) // P1 op (P2 op ... (Pn op init)) ( init op ... op pack ) // ((init op P1) op ... Pn-1) op Pn template<typename... T> auto fold_sum(T... s){ return (... + s); } template<typename... T> auto fold_sum1(T... s){ return ( s); } C++17 template<typename ...Args> void prt(Args&&... args) { (cout << ... << args) << '\n'; }
55
lvalue/rvalue Perfect forwarding
56
C++11 Perfect forwarding Note: Decoupling allocation and construction
a not completely correct implementation of emplace template< typename ... TList> iterator emplace( const_iterator p, TList && ... plist) { void * q = /* the space for the new element */; value_type * r = new( q) value_type( plist ...); /* ... */ } Note: Decoupling allocation and construction new( q) - placement new run a constructor at the place pointed to by q returns q converted to value_type * a special case of user-supplied allocator with an additional argument q void * operator new( std::size, void * q) { return q; } C++11
57
C++11 Perfect forwarding
template< typename ... TList> iterator emplace( const_iterator p, TList && ... plist) { void * q = /* the space for the new element */; value_type * r = new( q) value_type( plist ...); /* ... */ } How the emplace arguments are passed to the constructor? Pass by reference for speed, but lvalue or rvalue? Pass an rvalue as rvalue-reference to allow move Never pass an lvalue as a rvalue-reference Properly propagate const-ness of lvalues Three ways of passing required: T &, const T &, T && The number of emplace variants would be exponential C++11
58
C++11 Perfect forwarding Reference collapsing rules
Applied only when template inference is involved “Forwarding reference”, also called "Universal reference" T && where T is a template argument template< typename T> void f( T && p); X lv; f( lv); When the actual argument is an lvalue of type X Compiler uses T = X &, type of p is then X & due to collapsing rules f( std::move( lv)); When the actual argument is an rvalue of type X Compiler uses T = X, type of p is X && C++11 X & & X & X && & X & && X && && X &&
59
C++11 Perfect forwarding
Forwarding a universal reference to another function template< typename T> void f( T && p) { g( p); } X lv; f( lv); If an lvalue is passed: T = X & and p is of type X & p appears as lvalue of type X in the call to g f( std::move( lv)); If an rvalue is passed: T = X and p is of type X && Inefficient – move semantics lost C++11
60
C++11 Perfect forwarding Perfect forwarding T = X & T = X
template< typename T> void f( T && p) { g( std::forward< T>( p)); } std::forward< T> is simply a cast to T && X lv; f( lv); T = X & std::forward< T> returns X & due to reference collapsing The argument to g is an lvalue f( std::move( lv)); T = X std::forward< T> returns X && The argument to g is an rvalue std::forward< T> acts as std::move in this case C++11
61
C++11 Perfect forwarding A correct implementation of emplace
template< typename ... TList> iterator emplace( const_iterator p, TList && ... plist) { void * q = /* the space for the new element */; value_type * r = new( q) value_type( std::forward< TList>( plist) ...); /* ... */ } C++11
62
Forwarding references
Actual argument Formal argument p template< typename U> void f( U && p) Decoration Decorated p std::forward<U> lvalue T lvalue T T & U = T & std::forward<U> lvalue const T lvalue const T const T & U = const T & std::move rvalue T T && U = T rvalue T std::move std::forward<U>
63
Forwarding (universal) references
Forwarding references may appear as function arguments template< typename T> void f( T && x) { g( std::forward< T>( x)); } as auto variables auto && x = cont.at( some_position); Beware, not every T && is a forwarding reference It requires the ability of the compiler to select T according to the actual argument The use of reference collapsing tricks is (by definition) limited to T && The compiler does not try all possible T’s that could allow the argument to match Instead, the language defines exact rules for determining T C++11
64
Forwarding (universal) references
In this example, T && is not a forwarding reference template< typename T> class C { void f( T && x) { g( std::forward< T>( x)); } }; C<X> o; X lv; o.f( lv); // error: cannot bind an rvalue reference to an lvalue The correct implementation template< typename T2> void f( T2 && x) { g( std::forward< T2>( x)); C++11
65
Example – storing values of any type
Goal: Hand-made functor corresponding to the following lambda T may also be a string – avoid unnecessary copying [p](T & x){ x += p; } Naive approach template< typename T> class ftor { public: void ftor( T && p) : p_( std::forward< T>( p)) {} void operator()( T & x) { x += p_; } private: T p_; }; Does not work well auto f1 = ftor< std::string>( “Hello”); // works, passed by moving std::string s = “Hello”; auto f2 = ftor< std::string>( s); // does not work: can’t bind rvalue reference p C++11
66
Example – storing values of any type
A better implementation template< typename T> class ftor { public: template< typename T2> void ftor( T2 && p) : p_( std::forward< T2>( p)) {} void operator()( T & x) { x += p_; } private: T p_; }; Everything works auto f1 = ftor< std::string>( “Hello”); // passed as const char * && to conversion std::string s = “Hello”; auto f2 = ftor< std::string>( s); // passed as std::string & to copy-ctor auto f3 = ftor< std::string>( std::move( s)); // passed as std::string && to move-ctor But why the user needs to specify std::string explicitly? auto f2 = make_ftor( s); C++11
67
Example – storing values of any type
A class template< typename T> class ftor { /*...*/ T p_; }; and its wrapper function (naive attempt) template< typename T> ftor<T> make_ftor( T && p) // must use forwarding reference { return ftor<T>( std::forward< T>(p)); } This implementation is wrong and dangerous! std::string s = “Hello”; std::for_each( b, e, make_ftor( s)); // stores std::string & - works even faster std::vector< std::string> v = { “Hello” }; auto f = make_ftor( v.back()); // stores std::string & v.pop_back(); std::for_each( b, e, f); // crash!!! Such implementation may be useful, but users must know that it may store by reference C++11
68
Example – storing values of any type
A class template< typename T> class ftor { /*...*/ T p_; }; and its correct wrapper function template< typename T> ftor<std::remove_reference_t<T>> make_ftor( T && p) { return ftor<std::remove_reference_t<T>>( std::forward< T>(p)); } Shorter syntax { return { std::forward< T>(p)}; // if ctor is not explicit, {} may be omitted C++14: auto with return values auto make_ftor( T && p) C++14
69
Example – storing values of any type
How std::remove_reference_t is implemented? class template declaration (traits) template< typename T> class remove_reference; partial specialization of class template convention: “type” is the “return value” of type-returning traits template< typename U> class remove_reference< U &> { using type = U; }; C++11: type templates convention: suffix “_t” for type-returning traits template< typename T> using remove_reference_t = typename remove_reference<T>::type; C++11
70
Example – storing values of any type
Another example of traits (simplified) class template declaration (traits) template< typename T> class extent; partial specialization of class template convention: “value” is the “return value” of constant-returning traits template< typename U, size_t e> class remove_reference< U[e]> { static constexpr size_t value = e; }; C++14: variable templates convention: suffix “_v” for constant-returning traits template< typename T> using extent_v = extent<T>::value; And many others in <type_traits> C++11
71
Example – storing values of any type
template< typename T> class ftor { template< typename T2> void ftor( T2 && p); }; C++17: deduction guides template< typename T2> ftor( T2 && p) -> ftor< std::remove_reference_t< T2>>; Allows use of this syntax: std::string s = “hello”; ftor x( s); auto y = ftor( s); Wrapper functions no longer needed C++17
72
Example – storing values of any type
A class template< typename T> class ftor { public: template< typename T2> void ftor( T2 && p) : p_( std::forward< T2>( p)) {} void operator()( T & x) { x += p_; } private: T p_; }; and its wrapper function template< typename T> auto make_ftor( T && p) { return ftor<std::remove_reference_t<T>>( std::forward< T>(p)); } still does not work std::vector< std::string> v; std::for_each( v.begin(), v.end(), make_ftor( “Hello”)); ftor<const char *>::operator() requires const char * & C++11
73
Example – storing values of any type
The correct implementation is template< typename T> class ftor { public: template< typename T2> void ftor( T2 && p) : p_( std::forward< T2>( p)) {} template< typename T3> void operator()( T3 && x) { x += p_; } private: T p_; }; Note: use forwarding reference T3 && instead of lvalue reference T3 & this allows functionality on containers producing fake references (like vector< bool>) C++11
74
std::tuple Perfect forwarding
75
the std::tuple template
template <typename ... Types> class tuple { public: template< typename ... Types2> tuple( const Types2 & ...); /* black magic */ }; example using my_tuple = tuple< int, double, int>; my_tuple t1( 1, 2.3, 4); C++11: <utility>
76
the std::tuple template
template <typename ... Types> class tuple /*...*/; element access: get template < size_t I, typename ... Types> /*...???...*/ get( tuple< Types ...> & t); example using my_tuple = tuple< int, double, int>; my_tuple t1( 1, 2.3, 4); double v = get< 1>( t1); C++11: <utility>
77
the std::tuple template
template <typename ... Types> class tuple /*...*/; determining element type: tuple_element this is a traits template template < size_t I, typename T> struct tuple_element { using type = /* black magic */; }; type alias for easier use template < size_t I, typename T> using tuple_element_t = typename tuple_element< I, T>::type; example using my_tuple = tuple< int, double, int>; using alias_to_double = typename tuple_element< 1, my_tuple>::type; using alias2_to_double = tuple_element_t< 1, my_tuple>; C++11: <utility>
78
Traits, policies, tags, etc.
Class/struct template not designed to be instantiated into objects; contents limited to: type definitions (via typedef/using or nested struct/class) constants (via static constexpr) static functions Used as a compile-time function which assigns types/constants/run-time functions to template arguments 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 Functor 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 referenced in the body (closure) Tag class Empty class/struct Passed as run-time argument to function templates Used to distinguish between two functions (often constructors) with the same name and the same argument types
79
Traits, policies, tags, etc.
Class/struct template not designed to be instantiated into objects; contents limited to: type definitions (via typedef/using or nested struct/class) constants (via static constexpr) static functions Used as a compile-time function which assigns types/constants/run-time functions to template arguments Most frequently declared with one type argument, return info associated 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> 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;
80
Traits, policies, tags, etc.
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 { /*...*/ auto x = P::alloc(); /*...*/ }; struct my_policy { static void * alloc() { /*...*/ } }; container< my_policy> k;
81
Traits, policies, tags, etc.
Functor Class/struct containing non-static function named operator() 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 Usually passed as run-time argument to function templates 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 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) 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
82
Traits, policies, tags, etc.
Tag class Empty class/struct Passed as run-time argument to function templates Used to distinguish between two functions (often constructors) with the same name and the same argument types Example: C++17: std::parallel_policy namespace execution { class parallel_policy {}; // tag class inline constexpr parallel_policy par; // global constant } /*...*/ for_each(std::execution::parallel_policy, /*...*/); Usage: std::vector< int> k; std::for_each(std::execution::par, k.begin(), k.end(), [](int & i) { ++i; });
83
the std::tuple template
template <typename ... Types> class tuple /*...*/; determining element type: tuple_element_t template < size_t I, typename T> struct tuple_element { // traits template using type = /* black magic */; // the type of I-th element of T }; template < size_t I, typename ... Types> typename tuple_element< I, tuple< Types ...> >::type & get( tuple< Types ...> & t); C++11: <utility>
84
the std::tuple template – details and explanation
How to store the values in the tuple? template <typename ... Types> class tuple : Types ... {/**/}; This will not work! Non-class types may not be inherited The same class may not be inherited twice template <typename ... Types> class tuple : wrapper< Types> ... {/**/}; It does not solve the duplicity template <typename ... Types> class tuple : wrapper< I, Types> ... {/**/}; Where do we get the index I? We need recursion!
85
the std::tuple template – details and explanation
How to store the values in the tuple? We need recursion! Declaration template <typename ... Types> class tuple; Partial specialization – recursive inheritance template <typename T0, typename ... Types> class tuple< T0, Types ...> : public tuple< Types ...> { T0 v_; }; Explicit specialization – stop recursion template<> class tuple<> {};
86
the std::tuple template – details and explanation
How to retrieve I-th element from a parameter pack? Recursion again! Declaration template <std::size_t I, typename ... Types> class get_ith; Partial specialization – recursive inheritance template <std::size_t I, typename T0, typename ... Types> class get_ith< T0, Types ...> : public get_ith< I-1, Types ...> {}; Partial specialization – stop recursion and "return a type" This specialization has priority due to lower number of arguments template <typename T0, typename ... Types> class get_ith< 0, T0, Types ...> { using type = T0; }; What happens if I >= sizeof...(Types) ? No definition for get_ith< J> for J = I - sizeof...(Types)
87
the std::tuple template – details and explanation
Tuple element does not receive a parameter pack! using my_tuple = tuple< int, double, int>; using alias_to_double = typename tuple_element< 1, my_tuple>::type; Use specialization Declaration template <std::size_t I, typename T> class tuple_element; Partial specialization template <std::size_t I, typename ... Types> class tuple_element< I, tuple<Types ...>> : public get_ith< I, Types ...> {}; tuple_element is also implemented for pair and array
88
the std::tuple template – usage
How do we iterate over the elements of a tuple? template< typename T, typename F> void for_each_element( T && t, F && f) { for ( std::size_t I = 0; I < t::size(); ++ I) f( std::get< I>( t)); } No! std::get< I> requires constant I std::get< I> returns different types for different I
89
the std::tuple template – usage
How do we iterate over the elements of a tuple? template< typename ... Types, typename F> void for_each_element( std::tuple< Types ...> & t, F && f) { f( std::get< Types>( t)) ...; } std::get< T>(t) returns the element which has the type T from the tuple t but it fails when T is present more than once statement is not a correct place for pack expansion
90
the std::tuple template – usage
How do we iterate over the elements of a tuple? template< typename ... Types, typename F> void for_each_element( std::tuple< Types ...> & t, F && f) { sink( f( std::get< Types>( t)) ...); } trick: argument list may contain pack expansion template< typename ... Types> void sink( Types && ...) {} but argument list does not ensure left-to-right order of evaluation it still fails when T is present more than once
91
the std::tuple template – usage
How do we iterate over the elements of a tuple? std::get<T>(t) fails when T is present more than once we must use std::get<I>(t) with an index we need to generate the indices <0,...,sizeof...(Types)-1> C++14 library contains this: template< std::size_t ... Indexes> using index_sequence = /* just an empty class */; template< std::size_t N> using make_index_sequence = /* black magic */; The black magic ensures that make_index_sequence<N> == index_sequence< 0, 1, ..., N-1> But what it is good for?
92
the std::tuple template – usage
How do we iterate over the elements of a tuple? C++14 ensures that make_index_sequence<N> == index_sequence< 0, 1, ..., N-1> template< typename ... Types, typename F> void for_each_element( std::tuple< Types ...> & t, F && f) { helper( t, f, std::make_index_sequence< sizeof...( Types)>{}); } The third argument to helper is an empty temporary object Only the type of the object is referenced inside helper Compilers will (probably) optimize the object out template< typename T, typename F, typename ... Indexes> void helper( T & t, F && f, std::index_sequence< Indexes ...>) sink( f( std::get< Indexes>( t)) ...); beware: argument list does not ensure left-to-right order of evaluation
93
the std::tuple template – usage
How do we iterate over the elements of a tuple? argument list does not ensure left-to-right order of evaluation Before C++17 template< typename T, typename F, typename ... Indexes> void helper( T & t, F && f, std::index_sequence< Indexes ...>) { iterator_object<Indexes ...>{t, f}; } template< std::size_t I> empty_object { template< typename X> empty_object( X &&) {} // constructor to accept and drop anything }; template< typename ... Indexes> class iterator_object : empty_object< Indexes> ... { template< typename T, typename F> iterator_object( T && t, F && f) : empty_object< Indexes>( f( std::get< Indexes>( t))) ... {} base-class initializer list does ensure left-to-right order of evaluation similarly, braced initializer list may be used
94
the std::tuple template – usage
How do we iterate over the elements of a tuple? argument list does not ensure left-to-right order of evaluation With C++17 fold expressions template< typename T, typename F, typename ... Indexes> void helper( T & t, F && f, std::index_sequence< Indexes ...>) { (f( std::get<Indexes>(t)), ...); }
95
fold expressions - variadic templates
C++14 – recursion C simplification auto old_sum(){ return 0; } template<typename T1, typename... T> auto old_sum(T1 s, T... ts){ return s + old_sum(ts...); } template<typename... T> ????( T... pack) ( pack op ... ) // P1 op (P2 op ... (Pn-1 op Pn)) ( ... op pack ) // ((P1 op P2) op ... Pn-1) op Pn ( pack op ... op init ) // P1 op (P2 op ... (Pn op init)) ( init op ... op pack ) // ((init op P1) op ... Pn-1) op Pn template<typename... T> auto fold_sum(T... s){ return (... + s); } template<typename... T> auto fold_sum1(T... s){ return ( s); } C++17 template<typename ...Args> void prt(Args&&... args) { (cout << ... << args) << '\n'; }
96
Advanced use of templates
97
Templates are checked for validity
Validity of templates Templates are checked for validity (Optionally) on definition: syntactic correctness, correctness of independent names On instantiation: All rules of the language A template does not have to be correct for any combination of arguments It would be impossible in most cases Compilers check the correctness only for the arguments used in an instantiation Templates are difficult to test There is no mechanism yet to specify requirements on template arguments Trial-and-error approach Unreadable error messages when a template is incorrectly used C++20 will introduce concepts as a mechanism for constraining template arguments Instantiation of a class template does not invoke instantiation of all members A valid class template instance may contain invalid member functions Example: copy-constructor of vector<unique_ptr<T>>
98
Multiple templates with the same name
Class templates: one "master" template 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 Type templates only one definition (which may refer to different specializations of class template)
99
Type equivalence and templates
NPRG041 Programming in C /2017 David Bednárek
100
Type equivalence Equivalence is defined as
template< typename P> class File { // ... typename P::T get() { if ( P::binary ) } }; struct my_policy { typedef char T; static const bool binary = false; static const bool cached = true; File< my_policy> my_file; struct my_policy2 { typedef char T; static const bool binary = false; static const bool cached = true; }; void f( File< my_policy2> & p); f( my_file); // error Equivalence is defined as structs/classes are equivalent only if they are the same struct/class defined at the same position in the source file verbatim copies are NOT equivalent other type constructs (*,&,&&,[],()) are compared by contents typedef/using is transparent template instances are equivalent if they are the same template with equivalent arguments my_policy a my_policy2 are not equivalent, therefore File<my_policy> and File<my_policy2> are not equivalent
101
Employing type non-equivalence
template< typename P> class Value { typename P::T v; // ... }; struct mass { typedef double T; struct energy { Value< mass> m; Value< energy> e; e = m; // error Strong type system Differently named types may have the same contents are not compatible
102
Employing type non-equivalence
template< typename P> class Value { double v; // ... }; struct mass {}; struct energy {}; Value< mass> m; Value< energy> e; e = m; // error Strong type system Differently named types may have the same contents are not compatible Also works with empty classes Called tag classes
103
Employing type equivalence
template< int kg, int m, int s> class Value { double v; // ... }; template< int kg1, int m1, int s1, int kg2, int m2, int s2> Value< kg1+kg2, m1+m2, s1+s2> operator*( const Value< kg1, m1, s1> & a, const Value< kg2, m2, s2> & b); using Mass = Value< 1, 0, 0>; using Velocity = Value< 0, 1, -1>; using Energy = Value< 1, 2, -2>; Mass m; Velocity c; Energy e; e = m * c * c; // OK Instances of the template Value with the same values of numeric parameters are equivalent typedef/using is transparent
104
Abstract approach to templates
NPRG041 Programming in C /2017 David Bednárek
105
Abstract description of templates in C++
A template is a function evaluated by the compiler Its arguments may be: integral constants template< std::size_t N> /*...*/; types template< typename T> /*...*/; other templates (voilà: functional programming) template< template< /*...*/> class T> /*...*/; NPRG041 Programming in C /2017 David Bednárek
106
Abstract description of templates in C++
A template is a function evaluated by the compiler Its return value may be one of: an integral constant indirectly (convention: static member constant named "value") template< /*...*/> class F { static constexpr int value = /*...*/; }; directly [C++14] (convention: named "F_v" if defined as F::value) template< /*...*/> constexpr int F_v = /*...*/; a type a class - directly template< /*...*/> class F {/*...*/}; any type indirectly (convention: member named "type") template< /*...*/> class F { typedef /*...*/ type; }; any type directly [C++11] (convention: named "F_t" if defined as F::type) template< /*...*/> using F_t = /*...*/; a template indirectly template< /*...*/> class X { template< /*...*/> using type = /*...*/; }; a run-time function (a function template or a static member function of a class template) NPRG041 Programming in C /2017 David Bednárek
107
Abstract description of templates in C++
A template is a function evaluated by the compiler Its arguments may be: integral constants types templates, i.e. compile-time functions Its return value may (directly or indirectly) be one of: an integral constant a type a template, i.e. a compile-time function a run-time function A template may also "return" a "compile-time structure": template< /*...*/> class F { using A = /*...*/; static const int B = /*...*/; static int C(int x, int y) {/*...*/} }; NPRG041 Programming in C /2017 David Bednárek
108
Abstract description of templates in C++
There is a compile-time programming language inside C++ The language can operate on the following atomic "values": integral constants types run-time functions The operations available are integral operators of C++ (plus part of standard library marked constexpr) type constructions (creating pointers, references, arrays, classes, ...) defining a new run-time function (which may call others, including compile-time "values") The "values" may be combined into "structures" The compile-time language is functional no variables which could change their value "compile-time functions" (i.e. class templates) are first-class "values" NPRG041 Programming in C /2017 David Bednárek
109
constexpr constexpr functions in C++14 constexpr functions, variables:
The compiler will interpret the code of the function during compilation goto/asm/throw/try-catch prohibited Static local variables and dynamic allocation prohibited Only literal types allowed for variables Literal types Numbers (including floating-point arithmetic) Pointers and references They can point only to static variables (and C-style string literals) Arrays of literal types Structs/classes/unions containing literal types No virtual functions, some restrictions on constructors etc. constexpr functions, variables: Must be evaluated by compiler when used in template arguments etc. May be evaluated by compiler as an optimization elsewhere Cf. const variables, references, pointers, functions: Compiler prohibits run-time modification (via this access path only) NPRG041 Programming in C /2017 David Bednárek
110
Abstract description of templates in C++
There is a compile-time programming language inside C++ The compile-time language is functional There are Prolog-like features A compile-time function (i.e. a template) may be defined by several independent rules (using partial specialization) template< typename T, int N> class F; template< typename T> class F<T, 0> { /*...*/ }; template< typename U, int N> class F<U*,N> { /*...*/ }; template< typename P> class F<X<P>, 1> { /*...*/ }; There is unification of terms representing type arguments But unlike Prolog, there is no priority or try-next-if-failed mechanism With decltype(), function templates, and SFINAE, we can even emulate '!' NPRG041 Programming in C /2017 David Bednárek
111
Abstract description of templates in C++
There is a compile-time programming language inside C++ C++ types form a complex universe of compile-time "values" Classes (class/struct/union) are compared by name New unique "values" are generated by each occurrence of structure in source code Empty structures may be employed as unique opaque values (usually called tags) Other type constructs (*,&,&&,[],()) are compared by contents This allows unification in partial specializations Instances of the same class template are considered equal if their arguments are equal Aliases created by typedef/using are considered equal to their defining types NPRG041 Programming in C /2017 David Bednárek
112
Abstract description of templates in C++
There is a compile-time programming language inside C++ The output of the compile-time program is a run-time program Compile-time functions may return run-time types and run-time functions They may also generate static_assert and other error messages Elements used only during compile-time integral constants structures containing types, constants, static functions often called policy class even empty structures are important as tags templates perceived as compile-time functions those returning policy classes are often called traits Compile-time elements converted to run-time representations C++ types run-time functions NPRG041 Programming in C /2017 David Bednárek
113
Template tricks Compile-time arithmetics Rational numbers
template< intmax_t n, intmax_t d = 1> struct ratio; Example using platform = ratio_add<ratio<9>, ratio<3,4>>; static_assert( ratio_equal< platform, ratio< 975, 100>>::value, "?"); Evaluated by the compiler Including computing the greatest common divisor! C++11: <ratio>
114
Metaprogramming
115
Example Type transformation What it is good for?
Goal: Transform, e.g., this triplet ... using my_triplet = std::tuple< int, my_class, double>; ... into the triplet std::tuple< F(int), F(my_class), F(double)> What it is good for? column-stores: std::tuple< std::vector<int>, std::vector<my_class>, std::vector<double>> passing of references: std::tuple< int &, my_class &, double &>
116
Example How to invoke the transformation? Type transformation
Goal: Transform, e.g., this triplet ... using my_triplet = std::tuple< int, my_class, double>; ... into the triplet std::tuple< F(int), F(my_class), F(double)> How to invoke the transformation? Standard C++ library uses the “::type” convention: using my_transformed_triplet = type_transform< my_triplet, F>::type; In case of dependent types, „typename“ is required template< typename some_triplet> class X { using transformed_triplet = typename type_transform< some_triplet, F>::type; }; C++14 adds the “_t” convention: using my_transformed_triplet = type_transform_t< my_triplet, F>; No typename required
117
Example Type transformation How to write the F function?
Goal: Transform, e.g., this triplet ... using my_triplet = std::tuple< int, my_class, double>; ... into the triplet std::tuple< F(int), F(my_class), F(double)> using my_transformed_triplet = type_transform_t< my_triplet, F>; How to write the F function? Using the same “::type” convention: template< typename T> struct F { typedef std::pair< T, T> type; }; Templated aliases will not work here... using F = std::pair< T, T>; ... the alias F cannot be an argument of the type_transform_t template
118
Example Type transformation How to write the F function?
Goal: Transform, e.g., this triplet ... using my_triplet = std::tuple< int, my_class, double>; ... into the triplet std::tuple< F(int), F(my_class), F(double)> using my_transformed_triplet = type_transform_t< my_triplet, F>; How to write the F function? The “::type” convention allows specialization: template< typename T> struct F { typedef std::pair< T, T> type; }; template<> struct F< double> typedef std::complex< double> type;
119
Example Type transformation How to implement type_transform?
Goal: Transform, e.g., this triplet ... using my_triplet = std::tuple< int, my_class, double>; ... into the triplet std::tuple< F(int), F(my_class), F(double)> using my_transformed_triplet = type_transform_t< my_triplet, F>; How to implement type_transform? template< typename T, template< typename P> class F> struct type_transform; template< typename ... L, template< typename P> class F> struct type_transform< std::tuple< L ...>, F> { typedef std::tuple< typename F< L>::type ...> type; }; The first argument shall be tuple< L...> Specialization required to access the type list L The second argument is a template C++ allows only class/struct templates here
120
Example Working with tuple values
typedef std::tuple< int, my_class, double> my_triple; typedef type_transform< my_triple, my_type_function>::type my_transformed_triple; my_triple a, b; my_transformed_triple c; static_transform(a, b, c, my_value_function()); my_value_function is a polymorphic functor Polymorphic lambda in C++14? static_transform(a, b, c, [](auto && x, auto && y){ return std::make_pair( x, y); }); In this case, the lambda is not sufficient How to explicitly derive the returned type? How to use std::forward<???>( x)?
121
Example Polymorphic binary functor returning pair from two values
struct my_value_function { template< typename T1, typename T2> std::pair< typename std::remove_reference< T1>::type, typename std::remove_reference< T2>::type> operator()(T1 && x, T2 && y) const return std::make_pair( std::forward< T1>(x), std::forward< T2>(y)); } }; Observe the return type construction – remove_reference needed here
122
Example Polymorphic binary functor
Specialization possible (overloading operator()) Example: returning pair struct my_value_function { template< typename T1, typename T2> std::pair< typename std::remove_reference< T1>::type, typename std::remove_reference< T2>::type> operator()(T1 && x, T2 && y) const return std::make_pair( std::forward< T1>(x), std::forward< T2>(y)); } std::complex< double> operator()(double x, double y) const return std::complex< double>(x, y); };
123
Policy classes NPRG041 Programming in C /2017 David Bednárek
124
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 NPRG041 Programming in C /2017 David Bednárek
125
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 mixin1 { void function1() { ++var1; } }; mixin mixin2 { void function2() { function1(); } }; class final_class includes mixin1, mixin2 { int var1; }; NPRG041 Programming in C /2017 David Bednárek
126
Mixin Emulating mixins by inheritance: Problems
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 NPRG041 Programming in C /2017 David Bednárek
127
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, /*...*/> NPRG041 Programming in C /2017 David Bednárek
128
Mixin Global functions as a mixin The conversion operator
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! NPRG041 Programming in C /2017 David Bednárek
129
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> operator typename policy::final_const_class() const { /*...*/ } ...at the cost of declaring a policy class 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> { /*...*/ }; NPRG041 Programming in C /2017 David Bednárek
130
SFINAE NPRG041 Programming in C /2017 David Bednárek
131
Resolving function calls
Call expression f(args) is resolved by the compiler as: The identifier f is being looked for (in this order) In the scope of the function containing the call In the scope of the corresponding class (if inside a member function) In the scope of its base classes (except dependent base class names), recursively If found in more than one base class, the call is invalid, except where resolved by dominance Identifiers may be lifted to derived classes with “using base::ident;” declaration In the global/namespace scope Additional declarations may be visible via “using ns::ident;” or “using namespace ns;” If not found as type/variable/constant, argument-dependent-lookup is invoked Argument-dependent-lookup considers all namespaces related to types of call arguments If f is determined to be a function, function overload resolution is invoked to select one of the declarations Overload resolution may fail due to ambiguity
132
Resolving function calls
Overload resolution Applies to call expressions like f(args) where f is determined to be a function Applied in three independent cases: Global/namespace scope: All function declarations found by ADL Member function declarations in the same class (including those lifted by “using”) Operator invocation like “a1+a2”: Global/member cases mixed together All function and function template declarations with the given name are considered Phases: For templates, template parameters matching the call are deducted Deterministic mechanism defined by language, produces at most one result Deduction may fail Deducted template parameters may cause fail elsewhere in the function header Both kinds of failures lead to exclusion of the declaration from the candidate set (SFINAE) All remaining candidate functions (non-templates and succesfully instantiated templates) are checked Compatibility wrt. number and types of arguments in the call is verified Return type is NOT considered (i.e. checked wrt. the context of the call) If more than one candidate satisfies the compatibility rules, priority is determined “More specialized” templates have priority Declarations resulting in “cheaper implicit conversions” of arguments have priority Both sets of priority rules create only partial orderings – they may fail because of ambiguity
133
SFINAE Substitution Failure Is Not An Error Example:
namespace std { template< typename IT> typename iterator_traits<IT>::difference_type distance( IT a, IT b); }; template< typename T> float distance( const std::string & a, const std::string & b); std::string x = “Berlin”, y = “Paris”; std::cout << distance(x,y); std::distance is visible for the call distance(std::string,std::string) The return type iterator_traits<std::string>::difference_type is not defined If function template parameters derived from a function call cause an error when substituted into another function parameter type or the return type, this function template is excluded from the set of function declarations considered A similar definition applies to template specializations
134
SFINAE Substitution Failure Is Not An Error
The SFINAE rule is used (and misused) for dirty tricks std::enable_if<V,T> std::enable_if<true,T>::type === T std::enable_if<true>::type === void std::enable_if<false,T> does not define the member “type”, which invokes SFINAE std::enable_if_t<V,T> === typename std::enable_if<V,T>::type template< typename IT> enable_if_t< is_same_v< typename iterator_traits< IT>::iterator_category, random_access_iterator_tag>, typename iterator_traits< IT>::difference_type> distance( IT a, IT b) { return b – a; } ! is_same_v< typename iterator_traits< IT>::iterator_category, random_access_iterator_tag>, { for (ptrdiff_t n = 0; a != b; ++n, ++a); return n; }
135
Function template priority
More specialized function templates Defines priority of two template functions like template< args1> void f( formals1); // f1 template< args2> void f( formals2); // f2 Informally: f1 is more specialized than f2 if… … for each combination of types/constants assigned to args1… Instead of checking infinite number of types/constants, the compiler introduces a fictitious unique type/constant for each template parameter … the resulting sequence of (types of) formals1 … Determined by simply substituting the fictitious types/constants into types of formals1 Substitution failures are ignored … may be successfully used as actual arguments to f2 the function template argument deduction for f2 is successful Substitution failures are ignored, conversion failures are relevant This relation is not even a partial ordering The winner must be more specialized than all other candidates and all other candidates must not be more specialized than the winner Often there is no winner Partial specializations of class templates are selected using similar rules There are no conversions – simpler and more predictable behavior
136
Advanced use of SFINAE Enabling a function depending on policy
Auxiliary template to test convertibility template< typename policy_to, typename policy_from> struct is_convertible_policy : std::false_type {}; Partial specialization applicable when the target policy declares “convert_from” policy type template< typename policy_to> struct is_convertible_policy< policy_to, typename policy_to::convert_from> : std::true_type {}; Convenience interface (templated constant) static constexpr bool is_convertible_policy_v = is_convertible_policy< policy_to, policy_from>::value; Conditionally enabled conversion constructor std::enable_if placed in an additional anonymous template argument with a default value template< typename policy> class generic_iterator { template< typename policy2, typename = std::enable_if< is_convertible_policy_v< policy, policy2>>> generic_iterator(const generic_iterator< policy2> & b) { /*...*/ } }; NPRG041 Programming in C /2017 David Bednárek
137
Tag classes NPRG041 Programming in C /2017 David Bednárek
138
Employing type non-equivalence
template< typename P> class Value { double v; // ... }; struct mass {}; struct energy {}; Value< mass> m; Value< energy> e; e = m; // error Type non-equivalence Two classes/structs/unions/enums are always considered different even if they have the same contents Two instances of the same template are considered different if their parameters are different It also works with empty classes Called tag classes Usage: To distinguish types which represent different things using the same implementation Physical units Indexes to different arrays Similar effect to enum class
139
Employing type non-equivalence
template< std::size_t v> class const_v {}; void f( const_v< v>) { if constexpr ( v == 42 ) { /*...*/ } } void f( const_v< 0>) { /* ... */ Templated tag classes Carriers for compile-time values Called as f( const_v<42>{}); Advantages 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>{});
140
Employing type non-equivalence
template< class T, T v > struct integral_constant; auto f = [](auto && a){ if constexpr ( a == 42 ) { /* ... */ } f(a); // allows specialization }; std::integral_constant<T,v> instantiable class containing no data allows conversion to T which returns v
141
Example Iterating through n-tuple elements
template< typename A, typename B, typename C, typename F> void static_transform(A && a, B && b, C && c, F && f) { static const auto al = std::tuple_size< typename std::remove_reference< A>::type>::value; static const auto bl = std::tuple_size< typename std::remove_reference< B>::type>::value; static const auto cl = std::tuple_size< typename std::remove_reference< C>::type>::value; static_assert(al == bl && bl == cl, "tuples must be of equal length"); static_for_each_index< 0, cl>( transform_ftor< A, B, C, F>( std::forward< A>(a), std::forward< B>(b), std::forward< C>(c), std::forward< F>( f))); }
142
Example Static for-loop - interface template< std::size_t i>
struct static_index { static const std::size_t value = i; }; template< std::size_t b, std::size_t e, typename F> void static_for_each_index(F && f) // calls f( static_index< i>) for b <= i < e }
143
Example A functor for static_transform
template< typename A, typename B, typename C, typename F> struct transform_ftor { transform_ftor(A && a, B && b, C && c, F && f) : a_(std::forward< A>(a)), b_(std::forward< B>(b)), c_(std::forward< C>(c)), f_(std::forward< F>(f)) {} template< typename I> void operator()(I &&) static const auto i = I::value; std::get< i>(std::forward< C>( c_)) = f_( std::get< i>(std::forward< A>( a_)), std::get< i>(std::forward< B>( b_))); } private: A && a_; B && b_; C && c_; F && f_; };
144
Example Static for-loop - implementation
template< std::size_t b, std::size_t n> struct for_each_index_helper : for_each_index_helper< b, n - 1> { template< typename F> for_each_index_helper(F & f) : for_each_index_helper< b, n - 1>( f) f(static_index< b + n - 1>()); } }; template< std::size_t b> struct for_each_index_helper< b, 0> for_each_index_helper(F &) {} template< std::size_t b, std::size_t e, typename F> void static_for_each_index(F && f) for_each_index_helper< b, e - b> tmp(f);
145
Example Static for-loop – implementation without recursion
template< std::size_t b, typename S> struct for_each_index_helper2; template< std::size_t b, std::size_t ... L> struct for_each_index_helper2< b, std::integer_sequence< std::size_t, L ...> > { template< typename F> void call(F & f) (f( static_index< b + L>()), ...); } }; template< std::size_t b, std::size_t e, typename F> void static_for_each_index(F && f) for_each_index_helper2< b, std::make_index_sequence< e - b> >::call(f);
Similar presentations
© 2025 SlidePlayer.com Inc.
All rights reserved.