Allow much easier implemenation of templates with variable
arguments, like std::tuple
, std::function
, etc.
// loading ...
// loading ...
// T is a template parameter pack
sizeof...(T)
// count of types in T
A console that allows execution of arbitrary
std::function
s.
// loading ...
// loading ...
// loading ...
// loading ...
// loading ...
Based on "The Way of the Exploding Tuple"
// loading ...
// loading ...
// loading ...
T
, create a new one based on
T
T
determine its properties (traits):The general name for the type computational facilities. The standard
type traits are defined in type_traits
header.
Most of the type traits use template specialization.
template <typename T, T Value>
struct integral_constant {
static T value = Value;
};
typedef integral_contant<bool, false> false_type;
typedef integral_contant<bool, true> true_type;
template <typename T>
struct is_pointer : false_type
{};
template <typename T>
struct is_pointer<T*> : true_type
{};
template <typename T>
struct add_pointer
{
typedef T* type;
};
The resulting new type is defined in ::type
.
template <typename T>
struct remove_pointer
{
typedef T type;
};
template <typename T>
struct remove_pointer<T*>
{
typedef T type;
};
type_traits
is_arithmetic
is_fundamental
is_polymorphic
std::false_type
and std::true_type
for
custom type traitstype
The overload set is the set function overloads that are viable for resolving a call to a function
template <typename T>
void Copy(T* source, T* destination, size_t count);
template <typename T>
void Copy(T* source, T* destination, size_t count) {
for (size_t i = 0; i != count; ++i) {
destination[i] = source[i];
}
}
Can we do better?
template <typename T>
void Copy(T* source, T* destination, size_t count) {
if ((T is POD)) {
std::memcpy(destination, source, count * sizeof(T));
}
else {
for (size_t i = 0; i != count; ++i) {
destination[i] = source[i];
}
}
}
Overloads are distinguished and chosen based on their arguments count and type. So we can add another argument and overload on it.
struct isPOD {};
struct nonPOD {};
template <typename T>
void Copy(T* source, T* destination, size_t count, isPOD) {
std::memcpy(destination, source, count * sizeof(T));
}
template <typename T>
void Copy(T* source, T* destination, size_t count, nonPOD) {
for (size_t i = 0; i != count; ++i) {
destination[i] = source[i];
}
}
int a[10], b[10];
Copy(a, b, 10, isPOD());
// choses the memcpy implementation, because that is matching
// overload
std::string c[10], d[10];
Copy(c, d, 10, isPOD()); // oops
namespace detail
{
template <typename T>
void Copy(T* source, T* destination, size_t count,
std::true_type /*isPOD*/) {
std::memcpy(destination, source, count * sizeof(T));
}
template <typename T>
void Copy(T* source, T* destination, size_t count,
std::false_type /*isPOD*/) {
for (size_t i = 0; i != count; ++i) {
destination[i] = source[i];
}
}
}
template <typename T>
void Copy(T* source, T* destination, size_t count)
detail::Copy(source, destination, count, std::is_pod<T>());
}
Substitution Failure Is Not An Error
The compiler doesn't generate compiler error when a template paratemeter can not be substituted with a concrete type, it simply ignores the substutition.
template <typename T>
typename std::enable_if<std::is_pod<T>::value, void>::type
Copy(T* source, T* destination, size_t count) {
std::memcpy(destination, source, count * sizeof(T));
}
template <typename T>
typename std::enable_if<!std::is_pod<T>::value, void>::type
Copy(T* source, T* destination, size_t count) {
for (size_t i = 0; i != count; ++i) {
destination[i] = source[i];
}
}
template <bool Value, typename T>
struct enable_if {
};
template <typename T>
struct enable_if<true> {
typedef T type;
};
int s[10], d[10];
Copy(s, d, 10);
int
is POD
enable_if<is_pod<int>::value, void>::type
is
void
and the memcpy
overload of Copy
is viableenable_if<!is_pod<int>::value,
void>
, so the substitution fails and the compiler silently
ignores the for overloadtemplate <typename T>
typename std::enable_if<!std::is_pod<T>::value, void>::type
Copy(T* source, T* destination, size_t count) {
for (size_t i = 0; i != count; ++i) {
destination[i] = source[i];
}
}
// loading ...
Use std::enable_if
to control the overload set of generic
functions.
std::function
and
std::bind
How they can be implemented?
// loading ...
// loading ...
// loading ...
// loading ...
// loading ...
DelegateFun<void()>
and DelegateMethod<A, void()>
can not be a single class, because they have different
template arguments
Yet they have the same behavior - the void ()
function.
To make a usable delegate class, we need to erase the type difference between the types.
// loading ...
// loading ...
// loading ...
// loading ...
// loading ...
// loading ...
The pattern for type erasure is to define a class that exposes the API and forwards all calls to a polymorphic object, which implements the concrete API.
std::function
, boost::any
are probably the most famous examples
of type erasure.
The above implementation is pretty low performant.
The reason for this is that a lot of small objects get allocated and
always the actual function is away from the Delegate
instance we
have.
This can be resolved by storing the concrete DelegateFun
or
DelegateMethod
in a small buffer, directly in the Delegate
class.
Curiously Recurring Template Pattern
class Derived : public Base<Derived>
{
};
We have already seen that:
struct Renderer : std::enabled_shared_from_this<Renderer>
{};
struct Texture : RefCounted<Texture>
{};
The CRTP allows the base template to use the derived type - it can call methods, create/destroy instances and so on.
Using the derived class implementation though the base class interface
struct JSONParser {
virtual void array_begin() = 0;
virtual void array_end() = 0;
bool Parse() {
//...
char n = next();
switch (n) {
case '[' : array_begin(); break;
case ']' : array_end(); break;
// ...
}
}
};
struct JSONMinifier : JSONParser {
virtual void array_begin() override {
output.put('[');
}
virtual void array_end() override {
output.put(']');
}
};
We are making a virtual call for every token in the JSON file. And it is unlikely that we need more than a few different JSON parser types.
template <typename Derived>
struct JSONParser {
Derived* This() {
return static_cast<Derived*>(this);
}
bool Parse() {
//...
char n = next();
switch (n) {
case '[' : This()->array_begin(); break;
case ']' : This()->array_end(); break;
// ...
}
}
};
struct JSONMinifier : JSONParser<JSONMinifier> {
void array_begin() {
output.put('[');
}
void array_end() {
output.put(']');
}
};
Characterized by an in-class friend function declared in the base class of a CRTP
template <typename T>
class comparable {
friend bool operator > (const T& lhs, const T& rhs) {
return !(lhs < rhs);
}
friend bool operator == (const T& lhs, const T& rhs) {
return !(lhs < rhs || lhs > rhs);
}
};
struct Poly : public comparable<Poly> {
bool operator<(const Poly&rhs) const {
//...
}
}
A technique for using types (templates) to represent part of an expression.
The type (template) represents some kind of an operation with its operands and allows the result to be evaluated later or passed to a function (used itself as an operand).
// loading ...
template <typename T>
Polynom<T> operator+(const Polynom<T>& lhs, const Polynom<T>& rhs) {
auto power = std::max(lhs.power(), rhs.power();
Polynom<T> result(power);
for (auto i = 0; i != power; ++i) {
result.set(i, lhs.get(i) + rhs.get(i));
}
return result;
}
Polynom<int> a = { 1, 0, 1, 0, 1 }; // x^3 + x + 1
Polynom<int> b = { 2, 2, 0, 2, 3 }; // 3*x^3 + 2*x^2 + 2*x + 2
Polynom<int> c = { 42 };
auto d = a + b + c;
// compiled to:
// t1 = a + b
// t2 = t1 + c
// d = t2
auto d = a + b + c;
// With ET it compiled to:
// Sum(a,b) t1 = a + b
// Sum(Sum(a,b),c) t2 = t1 + c
// d = t2 // create Polynom from Sum(Sum(a, b), c)
// Pseudo code ahead
auto power = std::max(a.power(), b.power(), c.power())
Polynom<int> d(power);
for (auto i = 0; i != power; ++i) {
d.set(i, a.get(i) + b.get(i) + c.get(i));
}
// loading ...
// loading ...
// loading ...
// loading ...
Why does the Polynom
template have methods get
and set
instead
of operator[]
?