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 4 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 28 Seminar: Assignment #1 week 8 April 11 Seminar: Assignment #2 April 12: Deadline #1 week 10 April 25 Seminar: Assignment #3, Evaluation #1 April 26: Deadline #2 week 12 May 9 Seminar: Evaluation #2 May 10: Deadline #3 week 14 May 23 Seminar: Evaluation #3 Deadlines: Thursdays at 23:59 local time submit the required files to SIS late submissions = penalty points
4
Books C++11/14/17 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 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
15
Exception-safe programming
Bezpečné programování s výjimkami
16
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
17
Exception-safe programming
Smart pointers can solve some problems with exception safety void f() { auto a = std::make_unique< int[]>( 100); auto b = std::make_unique< int[]>( 200); g( a, b); } 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 }
18
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 T & operator=( const T & b) { if ( this != & b ) delete body_; body_ = new TBody( b.length()); copy( body_, b.body_); } return * this;
19
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 blocks When constructing a compound object, a part constructor may throw Array allocation Class constructors The implicit try block destructs previously constructed parts and rethrows
20
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"
21
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
22
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
23
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
24
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
25
Strong exception safety
Prepare-and-commit style Prepare-functions generate a token Tokens must be commited to produce observable change Commit-functions must not throw If not commited, 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
26
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
27
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
28
Templates
29
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;
30
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>>
31
Templates and compilation
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
32
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>
33
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
34
Variadic templates
35
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
36
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
37
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
38
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
39
lvalue/rvalue Perfect forwarding
40
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
41
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
42
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 &&
43
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
44
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
45
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
46
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>
47
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
48
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
49
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
50
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
51
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
52
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++11
53
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”)); uses ftor<const char *> which will only work with std::vector< const char *> C++11
54
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
55
std::tuple Perfect forwarding
56
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>
57
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>
58
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>
59
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
60
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;
61
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;
62
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
63
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; });
64
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>
65
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!
66
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<> {};
67
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)
68
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
69
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
70
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
71
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
72
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?
73
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
74
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
75
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)), ...); }
76
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'; }
77
Advanced use of templates
78
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>>
79
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)
80
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
81
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
82
Substitution Failure Is Not An Error
SFINAE Substitution Failure Is Not An Error Original problem: template< typename K> typename K::iterator end( K && k) { /*...*/ } // note: it is not the real std::end template< typename T> void end( T pid) { kill( pid); } "end(42)" matches both argument lists (with K=T=int) return type "int::iterator" is invalid the SFINAE rule forces the compiler to ignore the first definition of "end" if the return type were not invalid, the call to "end(42)" would fail due to ambiguity 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
83
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; }
84
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
85
Type equivalence and templates
NPRG041 Programming in C /2017 David Bednárek
86
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
87
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
88
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
89
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
90
Abstract approach to templates
NPRG041 Programming in C /2017 David Bednárek
91
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
92
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
93
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
94
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
95
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
96
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
97
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
98
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
99
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>
100
Metaprogramming
101
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 &>
102
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
103
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
104
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;
105
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
106
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)?
107
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
108
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); };
109
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))); }
110
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 }
111
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_; };
112
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);
113
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) sink( (f( static_index< b + L>()), 0) ...); } template< typename ... X> void sink( X && ...) {} }; 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.