This presentation by Denys Petrov (Senior Software Engineer, Consultant, GlobalLogic, Kharkiv) was delivered at GlobalLogic Kharkiv C++ Workshop #1 on September 14, 2019.
In this talk were covered:
- Historical development of basic C ++ template syntax.
- Use template syntax capabilities to introduce compile-time introspection and reflection.
- Browse existing SFINAE libraries and implement your own.
Conference materials: https://www.globallogic.com/ua/events/kharkiv-cpp-workshop/
5. 5
Templates of:
● functions
● classes
Kind of template parameters:
● type
● non-type integral types only
● template
template <typename T>
void function() {}
template <typename T>
class Class {};
template <typename T>
template <int i>
template <template < typename > class T>
Keywords:
● template
● typename
● class
С++03
6. 6
class vs typename
typename
• introduced to replace class
keyword and to avoid confusions
• used in template context only
class
• initially used as template parameter
by Bjarne Stroustrup before official
C++98
• used for class declarations
Both keywords are interchangeable except two cases:
• template <template < typename > class T> // until С++17
• typename T::type;
7. 7
Parameter pack and operator sizeof...
template <typename... T>
void function(T... args)
{
const int num = sizeof...(args);
const int sum = (args + ...);
}
constexpr int x = 42;
constexpr int pow2(int n)
{
return n * n;
}
Keyword constexpr
С++11
New context for keyword using
using AliasType = MyClass;
using AliasType = MyClass <int>;
template<typename T>
using AliasType = MyClass <T>;
int x = 42;
decltype(x) var1; // int
int& function(int);
decltype(function(x)) var2; // int&
Keyword decltype
8. 8
typedef vs using
typedef
assign synonyms to:
• types
• fully specialized templates
typedef int Int;
typedef void (*FuncPtr) (int);
typedef typename std::vector < int >::iterator IteratorInt;
template<typename T> using Vector = std::vector < T >;
template<typename T> using Iterator = typename std::vector < T >::iterator;
using Int = int;
using FuncPtr = void (*) (int);
using IteratorInt = typename std::vector < int >::iterator;
using
assign aliases to:
• types
• fully specialized templates
• partially specialized templates
10. 10
Added templates of variables
//function
template <typename T>
void function() {}
//class
template <typename T>
class Class {};
//variables
template <typename T>
const T PI = static_cast <T> (3.14159265);
template <typename T>
constexpr int value = T::value;
С++14
11. 11
Added operator constexpr if
if constexpr (true)
{
// compiled
}
else
{
// discarded
}
if constexpr (sizeof...(args) == 2)
{
// ...
}
if constexpr (false)
{
//static_assert(false);
static_assert(std::dependent_false<T>::value);
}
С++17
12. 12
Template auto as unknown non-type parameter
//function
template <auto value>
void function() {}
//class
template <auto value>
class Class {};
//variadic class
template <auto... value>
class Class {};
Class <42, 'X', true> //heterogeneous
//function
template <typename T, T value>
void function() {}
//class
template <typename T, T value>
class Class {};
//variadic class
template <typename T, T... value>
class Class {};
Class <int, 42, 43, 44> //homogeneous
C++17 C++14
С++17
13. 13
1. Debug > Project Properties > C/C++ > Language > C++ Language Standard
1. Debug > Project Properties > General > C++ Language Standard
Changing Standard in Visual Studio
1
2
3
4
16. 16
SFINAE - SUBSTITUTION FAILURE IS NOT AN ERROR
• Abbreviation introduced by David Vandevoorde
• Based on a template syntax
• Result of the order of a template substitution
• Make possible a reflection
• Constrains the range of applied types
19. 19
Introspection and Reflection
Metaprogramming (or Generic Programming) is a technique in which
program have the ability to generate, analyze or transform code using
compile-time computation.
Reflection is the ability of a program to examine, introspect, and modify its
own structure and behavior.
Introspection is the ability of a program to examine the type or properties of
an object at runtime.
Runtime for metaprogramming is a compile-time.
20. 20
Correct Incorrect
template <typename T1, typename T2>
class Class { };
template <typename T>
class Class <T, typename T::NestedType> { };
template <typename T>
class Class : T::NestedType1 { };
template <typename T>
class Class <const T> : T::NestedType2 { };
template <typename T,
typename = typename T::NestedType>
class Class { };
template <typename T>
class Class
{ using type = typename T::NestedType };
template <typename T,
typename = typename T::NestedType>
void function ()
{
}
template <typename T>
void function ()
{
typename T::NestedType var;
}
template <typename T>
auto function (T t) -> decltype(t.foo())
{
return t.foo();
}
template <typename T>
auto function (T t)
{
return t.foo();
}
Use Cases
21. 21
Object Inspection
• Partial and full specialization
- Classes
- Functions
• Conditional expressions
- Boolean types
- Logical operators
- Predicates
• Transformations
- Modifiers
• Conditional statements
- If operator
- Switch operator
• Tag dispatching
23. 23
Class Template Specialization
Primary class template:
template <typename T1, typename T2>
class Class { };
Full specialization:
template <>
class Class <int, char> { };
Partial specialization:
template <typename T>
class Class <int, const T> { };
Examples:
Class <const char, int&> c1;
Class <int, const int*> c2;
Class <char*, int[10]> c3;
24. 24
template <typename T>
class Class {};
template <typename T>
class Class <const T *> {};
template <typename T>
class Class <const T> {};
Class <int> -> Class <T> -> T = int
Class <const int> -> Class <const T> -> T = int
Class <int * const> -> Class <const T> -> T = int*
Class <const int *> -> Class <const T *> -> T = int
Class <const int * const> -> Class <const T> -> T = const int*
Template Type Deduction
Instantiation Substitution Deduction
29. 29
Instantiated type: const volatile int * const volatile
1. Class <const volatile T * const volatile> -> T = int
1. Class <volatile T * const volatile> -> T = const int
1. Class <const T * const volatile> -> T = volatile int
1. Class <T * const volatile> -> T = const volatile int
1. Class <const volatile T> -> T = const volatile int *
1. Class <volatile T> -> T = const volatile int * const
1. Class <const T> -> T = const volatile int * volatile
1. Class <T> -> T = const volatile int * const volatile
* error C2752: more than one partial specialization matches the template argument list
Substitution Preference Order
*
*
30. 30
Partial specialization with lower or equal amount of parameters:
template <typename T>
class Class <int, const T> { }; // ONE type
One More Class Template Specialization
Partial specialization with bigger amount of parameters:
template <template <typename, typename> typename T1, typename T2, typename T3>
class Class <???> { }; // THREE types (one of them is a template type)
template <typename T, size_t value>
class Class <???> { }; // ONE type, ONE variable
template <typename T1, typename T2, typename... T3>
class Class <???> { }; // Variable amount of types (minimum TWO)
Primary class template:
template <typename T1, typename T2>
class Class { }; // TWO types
31. 31
One More Class Template Specialization
Partial specialization with bigger amount of parameters:
template <template <typename, typename> typename T1, typename T2, typename T3>
class Class <T1 <T2, T3>, T2&> { }; // std::vector<int>
template <typename T, size_t value>
class Class <T[value], T> { }; // char[10]
template <typename T1, typename T2, typename... T3>
class Class <T1(*)(T3...), T2> { }; // int(*)(float&, const int)
Partial specialization with lower or equal amount of parameters:
template <typename T>
class Class <int, const T> { };
Primary class template:
template <typename T1, typename T2>
class Class { };
32. 32
Function Template Specialization
Function template:
template <typename T>
void func (T) { };
Full specialization:
template <>
void func <int *> (int *) { };
Example:
int i = 42;
func(i);
func(&i);
“Partial” specialization (Overloading):
template <typename T>
void func (T *) { };
39. 39
Logical operators. Conjunction.
&&
Traditional Programming
template <typename BOOL1, typename BOOL2>
struct AND
{
using result = False;
};
template <>
struct AND <True, True>
{
using result = True;
};
Metaprogramming
40. 40
Logical operators. Disjunction.
||
Traditional Programming Metaprogramming
template <typename BOOL1, typename BOOL2>
struct OR
{
using result = True;
};
template <>
struct OR <False, False>
{
using result = False;
};
41. 41
Logical operators. Inversion.
!
Traditional Programming
template <typename BOOL>
struct NOT
{
using result = False;
};
template <>
struct NOT <False>
{
using result = True;
};
Metaprogramming
42. 42
Example
typename AND <
typename OR <
True,
False
>::result,
typename NOT <
False
>::result
>::result
OR
NOT
AND
( true || false ) && ( ! false )
Traditional Programming
typename AND < typename OR < True, False >::result, typename NOT < False >::result >::result;
Metaprogramming
43. 43
Predicates. Qualifier Check.
bool IsConst(T);
Traditional Programming Metaprogramming
template <typename T>
struct IsConst
{
using result = False;
};
template <typename T>
struct IsConst <const T>
{
using result = True;
};
44. 44
Predicates. Reference Check.
bool IsReference(T);
Traditional Programming Metaprogramming
template <typename T>
struct IsReference
{
using result = False;
};
template <typename T>
struct IsReference <T&>
{
using result = True;
};
45. 45
Class Template With Default Parameter
template <typename T, typename = void>
struct Class { };
template <typename T>
struct Class <T, void> { };
Class <int>; -> struct Class <T, void>
Class <int, void>; -> struct Class <T, void>
Class <int, char>; -> struct Class
54. 54
If Statement
if (<bool expression>)
{
}
Traditional Programming Metaprogramming
template <typename T>
struct IF
{
};
template <>
struct IF <True>
{
using allow = void;
};
55. 55
If Statement Usage
You can put IF statement to the next places:
● Return type
template <typename T>
typename IF <True>::allow function (T) { }
● Function argument
template <typename T>
void function (T, typename IF <True>::allow* = 0) { }
● Default template argument
template < typename T, typename = typename IF <True>::allow >
void function (T) { }
template < typename T, typename = typename IF <True>::allow >
class Class { };
56. 56
Example
if( IsConst(T) || IsReference( RemoveConst(T) ) ) && ( ! IsConst( AddConst(T) ) )
{
function(t);
}
Traditional Programming
template < typename T, typename = typename IF < typename AND < typename OR < typename
IsConst <T>::result, typename IsReference < typename RemoveConst <T>::result
>::result >::result, typename NOT < typename IsConst < typename AddConst <T>::result
>::result >::result >::result >::allow >
void function (T)
{
}
Metaprogramming
57. 57
Example
template < typename T, typename = typename IF < typename AND < typename OR < typename
IsConst <T>::result, typename IsReference < typename RemoveConst <T>::result >::result
>::result, typename NOT < typename IsConst < typename AddConst <T>::result >::result
>::result >::result >::allow >
void function (T)
{
}
if( IsConst(T) || IsReference( RemoveConst(T) ) ) && ( ! IsConst( AddConst(T) ) )
{
function(t);
}
Traditional Programming
Metaprogramming
60. 60
Meta-Map
struct key_1 { };
struct key_2 { };
template <typename KEY> class MapTypes { };
template <> struct MapTypes <key_1>
{ using Value = int; };
template <> struct MapTypes <key_2>
{ using Value = float; };
template <typename T> struct MapTypes <const T>
{ using Value = const int; };
template <typename ARRAY, size_t size>
struct MapTypes <ARRAY[size]>
{ using Value = void*; };
Example:
typename MapTypes <???> ::Value x;
template <int KEY> class MapVars { };
template <> struct MapVars <0>
{ static constexpr auto value = 42; };
template <> struct MapVars <1>
{ static constexpr auto value = 3.14; };
template <> struct MapVars <2>
{ static constexpr auto value = “Hello”; };
template <> struct MapVars <3>
{ static constexpr auto value = nullptr; };
Example:
auto x = MapVars <???> ::value;
61. 61
Switch Statement. Cases.
template <bool Value, int Index, int Size = 0>
struct CASE : Bool <Value>
{
static constexpr int index = Index;
static constexpr int size = Size;
};
template <int Size>
using CASE_DEFAULT = CASE <false, 0, Size>;
template <typename T, int index>
struct MyCases : CASE_DEFAULT <4> {};
template <typename T>
struct MyCases <T, 1> : CASE <IsConst <T>, 1> {};
template <typename T>
struct MyCases <T, 2> : CASE <IsReference <T>, 2> {};
template <typename T>
struct MyCases <T, 3> : CASE <HasMethodFoo <T>, 3> {};
62. 62
Switch Statement
template <template <typename, int> typename Cases, typename T, int size = Cases <T, 0>::size>
struct SWITCHImpl
{
static constexpr int index = size > 0 ?
( Cases <T, size>::value ? Cases <T, size>::index : SWITCHImpl <Cases, T, size - 1>::index ) : 0;
};
template <template<typename, int> typename Cases, typename T>
struct SWITCHImpl <Cases, T, 0>
{
static constexpr int index = 0;
};
template <template<typename, int> typename Cases, typename T>
constexpr int SWITCH = SWITCHImpl <Cases, T>::index;
Start
Go through all of
the cases using argument T
Find first true
condition
return index of the
condition
End
63. 63
Switch Statement Usage
if (IsConst <T>)
{
Class <T, 1> c;
}
else if (IsReference <T>)
{
Class <T, 2> c;
}
else if (HasMethodFoo <T>)
{
Class <T, 3> c;
}
Traditional Programming Metaprogramming
template <typename T, int X>
struct Class { static_assert(false); /*error*/ };
template <typename T>
struct Class <T, 1> { /* implementation #1 */ };
template <typename T>
struct Class <T, 2> { /* implementation #2 */ };
template <typename T>
struct Class <T, 3> { /* implementation #3 */ };
Example
Class <T, SWITCH <MyCases, T>> c;
64. 64
Switch Statement Usage. C++17.
/*
...
a lot preceding stuff...
...
*/
template <typename T>
void function (T)
{
Class <T, SWITCH <MyCases, T>> c;
}
C++14 C++17
template <typename T>
void function (T)
{
if constexpr (IsConst <T>)
{
Class <T, 1> c;
}
else if constexpr (IsReference <T>)
{
Class <T, 2> c;
}
else if constexpr (HasMethodFoo <T>)
{
Class <T, 3> c;
}
else
{
static_assert(false); // error
}
}
65. 65
Switch Statement For Functions
if (IsConst <T>)
{
function1 ();
}
else if (IsReference <T>)
{
function2 ();
}
else if (HasMethodFoo <T>)
{
function3 ();
}
Traditional Programming Metaprogramming
template <int index> struct Tag { };
template <typename T>
void function (Tag <0>)
{ static_assert(template_false <T>); }
template <typename T>
void function (Tag <1>) { /* implementation #1 */ }
template <typename T>
void function (Tag <2>) { /* implementation #2 */ }
template <typename T>
void function (Tag <3>) { /* implementation #3 */ }
Example
function <T> ( Tag <SWITCH <MyCases, T>> () );
66. 66
Static Assert at Compile-time
Compiler generates an error every time while meeting a false condition.
Use variable template to make compiler generate an error only when it
instantiates the template. It allows to avoid an instant error.
Wrong
template <typename T>
struct Class
{
static_assert(false);
};
Correct
template <typename T>
constexpr bool template_false = false;
template <typename T>
struct Class
{
static_assert(template_false <T>);
};
72. 72
std::advance
template <class Iterator, class Diff>
void advance (Iterator & iter, Diff offset)
{
static_assert (_Is_input_iter_v <Iterator>, "next requires input iterator");
if constexpr (_Is_random_iter_v <Iterator>)
{
iter += offset;
}
else
{
if constexpr (is_signed_v <Diff>)
{
if constexpr (_Is_bidi_iter_v <Iterator>)
{
for (; offset < 0; ++offset, --iter)
}
else
{
static_assert (offset >= 0, "negative advance of non-bidirectional iterator");
}
}
for (; 0 < offset; --offset, ++iter);
}
}
73. 73
Use SFINAE when
● Diagnose and warn about wrong code usage
● Create self-sufficient common components
● Create ecosystem of objects, common algorithms, libraries
● It is hidden from code users
● It helps to use the code easier
● There is someone who can review and maintain your code
74. 74
Acknowledgement
● youtube.com (CppCon 2017: Arthur O'Dwyer “A Soupçon of SFINAE”)
● youtube.com (Compile-time type introspection using SFINAE)
● youtube.com (C++Now 2018: Agustín Bergé “SFINAE: Substitution Failure Is Not An Error”)
● youtube.com (Compile-time type introspection using SFINAE)
● cppreference.com (Templates)
● habr.com (Упрощение кода с помощью if constexpr в C++17)
● habr.com (Жизнь во время компиляции)
● wikipedia.org (SFINAE)
● isocpp.org (Templates)
● Visual Studio STL sources