From e6a895efa151ffd181f84c5e097ffa136f07121c Mon Sep 17 00:00:00 2001 From: Bernhard Manfred Gruber Date: Tue, 8 Mar 2022 17:54:39 +0100 Subject: [PATCH 1/3] Small improvements to the expert chapter --- talk/expert/perfectforwarding.tex | 297 ++++++++++++++++-------------- talk/expert/sfinae.tex | 136 ++++++++------ talk/expert/variadictemplate.tex | 58 ++++-- 3 files changed, 279 insertions(+), 212 deletions(-) diff --git a/talk/expert/perfectforwarding.tex b/talk/expert/perfectforwarding.tex index ecf27f04..e68c2a4c 100644 --- a/talk/expert/perfectforwarding.tex +++ b/talk/expert/perfectforwarding.tex @@ -3,28 +3,34 @@ %http://eli.thegreenplace.net/2014/perfect-forwarding-and-universal-references-in-c/ \begin{frame}[fragile] \frametitlecpp[11]{The problem} - Trying to write a generic wrapper function - \begin{cppcode*}{} - template - void wrapper(T arg) { - func(arg); - } - \end{cppcode*} + How to write a generic wrapper function? + \begin{block}{} + \begin{cppcode*}{} + template + void wrapper(T arg) { + // code before + func(arg); + // code after + } + \end{cppcode*} + \end{block} Example usage : \begin{itemize} - \item emplace\_back - \item make\_unique + \item \mintinline{cpp}{emplace_back} + \item \mintinline{cpp}{make_unique} \end{itemize} \end{frame} \begin{frame}[fragile] \frametitlecpp[11]{Why is it not so simple?} - \begin{cppcode*}{} - template - void wrapper(T arg) { - func(arg); - } - \end{cppcode*} + \begin{block}{} + \begin{cppcode*}{} + template + void wrapper(T arg) { + func(arg); + } + \end{cppcode*} + \end{block} \begin{alertblock}{What about references ?} what if func takes references to avoid copies ?\\ wrapper would force a copy and we fail to use references @@ -33,52 +39,60 @@ \begin{frame}[fragile] \frametitlecpp[11]{Second try, second failure ?} - \begin{cppcode*}{} - template - void wrapper(T &arg) { - func(arg); - } - wrapper(42); - // invalid initialization of - // non-const reference from - // an rvalue - \end{cppcode*} + \begin{block}{} + \begin{cppcode*}{} + template + void wrapper(T& arg) { + func(arg); + } + wrapper(42); + // invalid initialization of + // non-const reference from + // an rvalue + \end{cppcode*} + \end{block} \begin{alertblock}{} - const ref won't help : you may want to pass something non const\\ - and rvalue are not yet supported... + \begin{itemize} + \item \mintinline{cpp}{const T&} won't work when passing something non const + \item rvalues are not supported in either case + \end{itemize} \end{alertblock} \end{frame} \begin{frame}[fragile] \frametitlecpp[11]{The solution : cover all cases} - \begin{cppcode*}{} - template - void wrapper(T& arg) { func(arg); } + \begin{block}{} + \begin{cppcode*}{} + template + void wrapper(T& arg) { func(arg); } - template - void wrapper(const T& arg) { func(arg); } + template + void wrapper(const T& arg) { func(arg); } - template - void wrapper(T&& arg) { func(arg); } - \end{cppcode*} + template + void wrapper(T&& arg) { func(arg); } + \end{cppcode*} + \end{block}{} \end{frame} \begin{frame}[fragile] \frametitlecpp[11]{The new problem : scaling to n arguments} - \begin{cppcode*}{} - template - void wrapper(T1& arg1, T2& arg2) - { func(arg1, arg2); } - - template - void wrapper(const T1& arg1, T2& arg2) - { func(arg1, arg2); } - - template - void wrapper(T1& arg1, const T2& arg2) - { func(arg1, arg2); } - ... - \end{cppcode*} + \begin{block}{} + \begin{cppcode*}{} + template + void wrapper(T1& arg1, T2& arg2) + { func(arg1, arg2); } + + template + void wrapper(const T1& arg1, T2& arg2) + { func(arg1, arg2); } + + template + void wrapper(T1& arg1, const T2& arg2) + { func(arg1, arg2); } + ... + \end{cppcode*} + \end{block}{} \begin{alertblock}{Exploding complexity} 3$^{n}$ complexity\\ you do not want to try n = 5... @@ -86,99 +100,102 @@ \end{frame} \begin{frame}[fragile] - \frametitlecpp[11]{Reference collapsing in \cpp98} + \frametitlecpp[11]{Reference collapsing} \begin{block}{Reference to references} - They are forbidden in \cpp\\ - But still may happen + \begin{itemize} + \item They are forbidden, but some compilers allow them + \end{itemize} + \end{block} + \begin{block}{} \begin{cppcode*}{} template - void foo(T t) { - T& k = t; - ... - } + void foo(T t) { T& k = t; } // int& & int ii = 4; foo(ii); \end{cppcode*} \end{block} - \begin{exampleblock}{Practically} - all compilers were collapsing the 2 references - \end{exampleblock} -\end{frame} - -\begin{frame} - \frametitlecpp[11]{Reference collapsing in \cpp11} - \begin{block}{rvalues have been added} + \begin{block}{\cpp11 added rvalues} \begin{itemize} - \item what about int\&\& \& ? - \item and int \&\& \&\& ? + \item what about \mintinline{cpp}{int&& &} ? + \item and int \mintinline{cpp}{&& &&} ? \end{itemize} \end{block} - \begin{exampleblock}{\cpp11 standardization} - The rule is simple : \& always wins\\ - \&\& \&, \& \&\&, \& \& $\rightarrow$ \&\\ - \&\& \&\& $\rightarrow$ \&\& + \begin{exampleblock}{Rule} + \mintinline{cpp}{&} always wins\\ + \mintinline{cpp}{&& &, & &&, & &} $\rightarrow$ \mintinline{cpp}{&}\\ + \mintinline{cpp}{&& &&} $\rightarrow$ \mintinline{cpp}{&&} \end{exampleblock} \end{frame} \begin{frame}[fragile] \frametitlecpp[11]{rvalue in type-deducing context} - \begin{cppcode*}{} - template - void func(T&& t) {} - \end{cppcode*} - Next to a template parameter, \&\& is not an rvalue, but a ``universal reference''\\ - T\&\& actual type depends on the arguments passed to func + \begin{block}{} + \begin{cppcode*}{} + template + void func(T&& t) {} + \end{cppcode*} + \end{block} + Next to a template parameter, \mintinline{cpp}{&&} is not an rvalue, but a ``forwarding reference'' (aka. ``universal reference'')\\ + \mintinline{cpp}{T&&} actual type depends on the arguments passed to func \begin{itemize} - \item if an lvalue of type U is given, T is deduced to U\& + \item if an lvalue of type U is given, T is deduced to \mintinline{cpp}{U&} \item otherwise, collapse references normally \end{itemize} - \begin{cppcode*}{firstnumber=3} - func(4); // rvalue -> T&& is int&& - double d = 3.14; - func(d); // lvalue -> T&& is double& - float f() {...} - func(f()); // rvalue -> T&& is float&& - int foo(int i) { - func(i); // lvalue -> T&& is int& - } - \end{cppcode*} + \begin{block}{} + \begin{cppcode*}{firstnumber=3} + func(4); // rvalue -> T&& is int&& + double d = 3.14; + func(d); // lvalue -> T&& is double& + float f() {...} + func(f()); // rvalue -> T&& is float&& + int foo(int i) { + func(i); // lvalue -> T&& is int& + } + \end{cppcode*} + \end{block} \end{frame} \begin{frame}[fragile] \frametitlecpp[11]{std::remove\_reference} - Some template trickery removing reference from a type - \begin{cppcode*}{} - template< typename T > - struct remove_reference - {using type = T;}; - - template< typename T > - struct remove_reference - {using type = T;}; - - template< typename T > - struct remove_reference - {using type = T;}; - \end{cppcode*} - If {\ttfamily T} is a reference type, {\ttfamily remove\_reference::type} is the type referred to by T, - otherwise it is T. + Type trait to remove reference from a type + \begin{block}{} + \begin{cppcode*}{} + template + struct remove_reference { using type = T; }; + + template + struct remove_reference { using type = T; }; + + template + struct remove_reference { using type = T; }; + \end{cppcode*} + \end{block} + If {\ttfamily T} is a reference type, \mintinline{cpp}{remove_reference_t::type} is the type referred to by {\ttfamily T}, + otherwise it is {\ttfamily T}. \end{frame} \begin{frame}[fragile] \frametitlecpp[11]{std::forward} - Another template trickery keeping references and mapping non reference types to rvalue references - \begin{cppcode*}{} - template - T&& forward(typename std::remove_reference::type& t) - noexcept { - return static_cast(t); - } - \end{cppcode*} + Keeps references and maps non-reference types to rvalue references + \begin{block}{} + \begin{cppcode*}{} + template + T&& forward(typename std::remove_reference + ::type& t) noexcept { + return static_cast(t); + } + template + T&& forward(typename std::remove_reference + ::type&& t) noexcept { + return static_cast(t); + } + \end{cppcode*} + \end{block} \begin{block}{How it works} \begin{itemize} - \item if T is int, it returns int \&\& - \item if T is int\&, it returns int\& \&\& ie int\& - \item if T is int\&\&, it returns int\&\& \&\& ie int\&\& + \item if T is \mintinline{cpp}{int}, it returns \mintinline{cpp}{int&&} + \item if T is \mintinline{cpp}{int&}, it returns \mintinline{cpp}{int& &&} ie. \mintinline{cpp}{int&} + \item if T is \mintinline{cpp}{int&&}, it returns \mintinline{cpp}{int&& &&} ie. \mintinline{cpp}{int&&} \end{itemize} \end{block} \end{frame} @@ -186,29 +203,31 @@ \begin{frame}[fragile] \frametitlecpp[11]{Perfect forwarding} Putting it all together - \begin{cppcode*}{} - template - void wrapper(T&&... args) { - func(std::forward(args)...); - } - \end{cppcode*} - \begin{block}{How it works} + \begin{block}{} + \begin{cppcode*}{} + template + void wrapper(T&&... args) { + func(std::forward(args)...); + } + \end{cppcode*} + \end{block} + \begin{block}{} \begin{itemize} - \item if we pass an rvalue to wrapper (U\&\&) + \item if we pass an rvalue to wrapper (\mintinline{cpp}{U&&}) \begin{itemize} - \item arg will be of type U\&\& - \item func will be called with a U\&\& + \item arg will be of type \mintinline{cpp}{U&&} + \item func will be called with a \mintinline{cpp}{U&&} \end{itemize} - \item if we pass an lvalue to wrapper (U\&) + \item if we pass an lvalue to wrapper (\mintinline{cpp}{U&}) \begin{itemize} - \item arg will be of type U\& - \item func will be called with a U\& + \item arg will be of type \mintinline{cpp}{U&} + \item func will be called with a \mintinline{cpp}{U&} \end{itemize} - \item if we pass a plain value (U) + \item if we pass a plain value (\mintinline{cpp}{U}) \begin{itemize} - \item arg will be of type U\&\& (no copy in wrapper) - \item func will be called with a U\&\& - \item but func takes a U, so copy happens there, as expected + \item arg will be of type \mintinline{cpp}{U&&} (no copy in wrapper) + \item func will be called with a \mintinline{cpp}{U&&} + \item but func takes a \mintinline{cpp}{U&}, so copy happens there, as expected \end{itemize} \end{itemize} \end{block} @@ -216,11 +235,13 @@ \begin{frame}[fragile] \frametitlecpp[11]{Real life example} - \begin{cppcode*}{} - template - unique_ptr make_unique(Args&&... args) { - return unique_ptr - (new T(std::forward(args)...)); - } - \end{cppcode*} + \begin{block}{} + \begin{cppcode*}{} + template + unique_ptr make_unique(Args&&... args) { + return unique_ptr + (new T(std::forward(args)...)); + } + \end{cppcode*} + \end{block} \end{frame} diff --git a/talk/expert/sfinae.tex b/talk/expert/sfinae.tex index ff7d1d19..2814bd62 100644 --- a/talk/expert/sfinae.tex +++ b/talk/expert/sfinae.tex @@ -5,27 +5,29 @@ \frametitlecpp[11]{Substitution Failure Is Not An Error (SFINAE)} \begin{block}{The main idea} \begin{itemize} - \item substitution replaces template parameters with the provided types or values - \item if it leads to an invalid code, do not fail but try other overloads + \item substitution replaces template parameters with the provided arguments (types or values) + \item if it leads to invalid code, do not fail but try other overloads \end{itemize} \end{block} \begin{exampleblock}{Example} \begin{cppcode*}{} template void f(typename T::type arg) { ... } + void f(int a) { ... } f(1); // Calls void f(int) \end{cppcode*} \end{exampleblock} - Note : SFINAE will be largely superseded by concepts in \cpp20 + Note : SFINAE is largely superseded by concepts in \cpp20 \end{frame} \begin{frame}[fragile] \frametitlecpp[11]{decltype} \begin{block}{The main idea} \begin{itemize} - \item gives the type of the expression it will evaluate + \item gives the type of the result of an expression + \item the expression is not evaluated \item at compile time \end{itemize} \end{block} @@ -35,6 +37,7 @@ A a; decltype(a.x) y; // double decltype((a.x)) z = y; // double& (lvalue) + decltype(1 + 2u) i = 4; // unsigned int template auto add(T t, U u) -> decltype(t + u); @@ -47,11 +50,12 @@ \frametitlecpp[11]{declval} \begin{block}{The main idea} \begin{itemize} - \item gives you a ``fake reference'' to an object at compile time - \item useful for types that cannot be easily constructed + \item gives you a reference to a ``fake'' object at compile time + \item useful for types that cannot easily be constructed + \item use only in unevaluated contexts, e.g. inside \mintinline{cpp}{decltype} \end{itemize} \end{block} - \begin{exampleblock}{Example} + \begin{exampleblock}{} \begin{cppcode*}{} struct Default { int foo() const { return 1; } @@ -71,16 +75,18 @@ \frametitlecpp[11]{true\_type and false\_type} \begin{block}{The main idea} \begin{itemize} - \item encapsulate a constexpr boolean ``true'' and ``false'' + \item encapsulate a compile-time boolean as type \item can be inherited - \item constexpr \end{itemize} \end{block} \begin{exampleblock}{Example} \begin{cppcode*}{} - struct testStruct : std::true_type { }; - constexpr bool testVar = testStruct(); - bool test = testStruct::value; // true + struct truth : std::true_type { }; + + constexpr bool test = truth::value; // true + constexpr truth t; + constexpr bool test = t(); // true + constexpr bool test = t; // true \end{cppcode*} \end{exampleblock} \end{frame} @@ -105,9 +111,9 @@ template struct hasFoo().foo())> : std::true_type {}; - struct A{}; struct B{void foo();}; - static_assert(!hasFoo::value, "A has no Foo()"); - static_assert(hasFoo::value, "B has Foo()"); + struct A{}; struct B{ void foo(); }; + static_assert(!hasFoo::value, "A has no foo()"); + static_assert(hasFoo::value, "B has foo()"); \end{cppcode*} \end{exampleblock} \end{frame} @@ -127,10 +133,10 @@ struct B{void foo();}; struct C{int foo();}; - static_assert(!hasFoo::value, "A has no Foo()"); - static_assert(hasFoo::value, "B has Foo()"); - static_assert(!hasFoo::value, "C has Foo()"); - static_assert(hasFoo::value, "C has Foo()"); + static_assert(!hasFoo::value, "A has no foo()"); + static_assert(hasFoo::value, "B has foo()"); + static_assert(!hasFoo::value, "C has foo()"); + static_assert(hasFoo::value, "C has foo()"); \end{cppcode*} \end{exampleblock} \end{frame} @@ -138,17 +144,18 @@ \begin{frame}[fragile] \frametitlecpp[17]{Using \texttt{void\_t}} \begin{block}{Concept} - \begin{cppcode*}{gobble=2} - #include - template - using void_t = void; - \end{cppcode*} \begin{itemize} \item Maps a sequence of given types to void - \item Introduced in \cpp17 though trivial to copy to \cpp11 - \item Can thus be used in specialization to check the validity of an expression + \item Introduced in \cpp17 though trivial to implement in \cpp11 + \item Can be used in specializations to check the validity of an expression \end{itemize} \end{block} + \begin{block}{Implementation in header type\_traits} + \begin{cppcode*}{gobble=2} + template + using void_t = void; + \end{cppcode*} + \end{block} \end{frame} \begin{frame}[fragile] @@ -166,9 +173,9 @@ struct A{}; struct B{ void foo(); }; struct C{ int foo(); }; - static_assert(!hasFoo::value,"Unexpected Foo()"); - static_assert(hasFoo::value, "expected Foo()"); - static_assert(hasFoo::value, "expected Foo()"); + static_assert(!hasFoo::value,"Unexpected foo()"); + static_assert(hasFoo::value, "expected foo()"); + static_assert(hasFoo::value, "expected foo()"); \end{cppcode*} \end{exampleblock} \end{frame} @@ -180,7 +187,7 @@ template struct enable_if {}; template struct enable_if { using type = T; }; - template< bool B, typename T = void > + template using enable_if_t = typename enable_if::type; \end{cppcode*} \begin{itemize} @@ -190,9 +197,9 @@ \end{block} \begin{block}{is\_*$$/is\_*\_v$$ (float/signed/object/final/abstract/...)} \begin{itemize} - \item Checks whether T is ... - \item At compile time - \item Has member \texttt{value}, as boolean telling whether it was + \item Standard type traits in header \texttt{type\_traits} + \item Checks at compile time whether T is ... + \item Result in boolean member \texttt{value} \end{itemize} \end{block} \end{frame} @@ -205,35 +212,33 @@ >> - In& operator()( In& in ) const { return in; } + In& operator()(In& in) const { return in; } template - In& operator()( In* in ) const { + In& operator()(In* in) const { assert(in!=nullptr); return *in; } - } deref {}; + } deref{}; \end{cppcode*} \end{exampleblock} - \end{frame} - \begin{frame}[fragile] \frametitlecpp[11]{Back to variadic templated class} \begin{block}{The tuple get method} \begin{cppcode*}{} - template - struct elem_type_holder; + template + struct elem_type; template - struct elem_type_holder<0, tuple> { + struct elem_type<0, tuple> { using type = T; }; - template - struct elem_type_holder> { - using type = typename elem_type_holder - >::type; + template + struct elem_type> { + using type = typename elem_type + >::type; }; \end{cppcode*} \end{block} @@ -241,23 +246,42 @@ \begin{frame}[fragile] \frametitlecpp[11]{Back to variadic templated class} - \begin{block}{The tuple get method} + \begin{block}{The tuple get function} \begin{cppcode*}{} - template - typename std::enable_if_t>::type&> + template + typename std::enable_if_t>::type&> get(tuple& t) { - return t.m_tail; + return t.m_head; } - template - typename std::enable_if_t>::type&> + template + typename std::enable_if_t>::type&> get(tuple& t) { tuple& base = t; - return get(base); + return get(base); + } + \end{cppcode*} + \end{block} +\end{frame} + +\begin{frame}[fragile] + \frametitlecpp[17]{with if constexpr} + \begin{block}{The tuple get function} + \begin{cppcode*}{} + template + auto& get(tuple& t) { + if constexpr(I == 0) + return t.m_head; + else + return get(static_cast>(t)); } \end{cppcode*} \end{block} + \begin{exampleblock}{Best practice} + \begin{itemize} + \item \mintinline{cpp}{if constexpr} can replace SFINAE in many places. + \item It is usually more readable as well. Use it if you can. + \end{itemize} + \end{exampleblock} \end{frame} diff --git a/talk/expert/variadictemplate.tex b/talk/expert/variadictemplate.tex index 0d47fc8f..eb2e82a9 100644 --- a/talk/expert/variadictemplate.tex +++ b/talk/expert/variadictemplate.tex @@ -6,22 +6,21 @@ \frametitlecpp[11]{Basic variadic template} \begin{block}{The idea} \begin{itemize} - \item a template with an arbitrary number of parameters - \item ... syntax as in good old printf - \item using recursivity and specialization for stopping it + \item a template parameter accepting arbitrary many arguments + \item template parameter pack for e.g.\ types, function parameter packs for values, and expansions, details on \href{https://en.cppreference.com/w/cpp/language/parameter_pack}{cppreference} \end{itemize} \end{block} - \begin{exampleblock}{Practically} + \begin{exampleblock}{Recursive example} \begin{cppcode*}{} template - T adder(T v) { - return v; - } - template - T adder(T first, Args... args) { - return first + adder(args...); + T sum(T v) { return v; } + + template // temp. param. pack + T sum(T first, Args... args) { // func. param. pack + return first + sum(args...); // pack expansion } - long sum = adder(1, 2, 3, 8, 7); + long sum = sum(1, 2, 3, 8, 7); \end{cppcode*} \end{exampleblock} \end{frame} @@ -45,20 +44,43 @@ \end{frame} \begin{frame}[fragile] - \frametitlecpp[11]{Variadic templated class} + \frametitlecpp[17]{Fold expressions} + \begin{block}{The idea} + \begin{itemize} + \item reduces a parameter pack over a binary operator + \item details on \href{https://en.cppreference.com/w/cpp/language/fold}{cppreference} + \end{itemize} + \end{block} + \begin{exampleblock}{Example} + \begin{cppcode*}{} + template + T sum1(Args... args) { + return (args + ...); // unary fold over + + } + template + T sum2(Args... args) { + return (args + ... + 0); // binary fold over + + } + long sum = sum1(); // error + long sum = sum2(); // ok + \end{cppcode*} + \end{exampleblock} +\end{frame} + +\begin{frame}[fragile] + \frametitlecpp[11]{Variadic class template} \begin{block}{The tuple example, simplified} \begin{cppcode*}{} - template struct tuple {} + template + struct tuple {}; template struct tuple : tuple { - tuple(T t, Ts... ts) : - tuple(ts...), m_tail(t) {} - T m_tail; + tuple(T head, Ts... tail) : + tuple(tail...), m_head(head) {} + T m_head; }; - template <> struct tuple<>{}; - tuple t1(12.2, 42, "big"); \end{cppcode*} From 81d1c574b8001c12dfc8038097a19ce387d58daf Mon Sep 17 00:00:00 2001 From: Sebastien Ponce Date: Tue, 15 Mar 2022 10:12:39 +0100 Subject: [PATCH 2/3] make code smaller to fit in screen --- talk/expert/perfectforwarding.tex | 1 + 1 file changed, 1 insertion(+) diff --git a/talk/expert/perfectforwarding.tex b/talk/expert/perfectforwarding.tex index e68c2a4c..43540868 100644 --- a/talk/expert/perfectforwarding.tex +++ b/talk/expert/perfectforwarding.tex @@ -178,6 +178,7 @@ \frametitlecpp[11]{std::forward} Keeps references and maps non-reference types to rvalue references \begin{block}{} + \small \begin{cppcode*}{} template T&& forward(typename std::remove_reference From 33d69c744f3033f242fead80347f73fe53508612 Mon Sep 17 00:00:00 2001 From: Sebastien Ponce Date: Tue, 15 Mar 2022 10:12:55 +0100 Subject: [PATCH 3/3] remove spaces so that code fits in screen --- talk/expert/sfinae.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/talk/expert/sfinae.tex b/talk/expert/sfinae.tex index 2814bd62..161b8b72 100644 --- a/talk/expert/sfinae.tex +++ b/talk/expert/sfinae.tex @@ -184,7 +184,7 @@ \frametitle{SFINAE and the STL \hfill \cpp11/\cpp14/\cpp17} \begin{block}{enable\_if / enable\_if\_t} \begin{cppcode*}{gobble=2} - template struct enable_if {}; + template struct enable_if {}; template struct enable_if { using type = T; }; template