int min(int a, int b)
{
return a < b ? a : b;
}
double min(double a, double b)
{
return a < b ? a : b;
}
Same code for every arithmetic type
int min(int a, int b)
{
return a < b ? a : b;
}
double min(double a, double b)
{
return a < b ? a : b;
}
Same code for every arithmetic type
Use C macros
#define DEFINE_MIN(TYPE) \
TYPE min(TYPE a, TYPE b) \
{ \
return a <b ? a : b; \
}
DEFINE_MIN(int)
DEFINE_MIN(double)
DEFINE_MIN(float)
....
#undef min
Use a common base class for all types
const object& min(const object& a, const object& b)
{
return a.less_than(b) ? a : b;
}
C++ templates: similar to C macros, but have it generated by the compiler
Function templates provide a function behavior that can be called for different types: a function template represents a family of functions.
template <class T> // T is called the "template parameter"
const T& max(const T& a, const T& b)
{
return a <b ? a : b;
}
Or
template <typename T>
const T& max(const T& a, const T& b)
{
return a <b ? a : b;
}
int main()
{
int i1 = 3, i2 = 5;
std::string s1 = "math", s2 = "mathematics";
double d1 = 2.4, d2 = 4.9;
std::cout << ::max(i1, i2) << std::endl; // 5
std::cout << ::max(s1, s2) << std::endl; // mathematics
std::cout << ::max(d1, d2) << std::endl; // 4.9
return 0;
}
template <typename T>
const T& max(const T& a, const T& b)
{
return a <b ? a : b;
}
int res = ::max(3, 5);
has the semantics of the following code:
int max(const int& a, const int& b)
{
return a <b ? a : b;
}
Replacing template parameters by concrete types is called template instantiation.
std::complex<double> c1, c2; // complex does not provide operator<
::max(c1, c2); // ERROR at compile time
Templates are compiled twice:
The compiler needs the definition of the template at the time of instantiation. This breaks the usual split of declaration and definition of ordinary functions.
template <typename T>
const T& max(const T& a, const T& b);
max(4, 7); // OK, T is int for both argument
max(4, 3.2); // ERROR: first T is int, second T is double
Three ways to handle the error:
// Cast the arguments:
max(static_cast<double>(4), 3.2);
// Specify explicitly T:
max<double>(4, 3.2);
// Add more template parameters
template <class T1, class T2>
T1 max(const T1& a, const T2& b);
template <class T1, class T2>
T1 max(const T1& a, const T2& b);
// Drawback: the first arugment defines the return type
template <class RT, clss T1, class T2>
RT max(const T1& a, const T2& b);
// Drawback: RT cannot be deduced:
double d = max<double>(4, 3.2); // OK, T1 and T2 are deduced
template <class RT = T1, class T1, class T2>
RT max(const T1& a, const T2& b);
double d = max(3.2, 4); // OK, T1 and T2 are deduced, RT is defaulted
template <class T1, class T2>
auto max(const T1& a, const T2& b) -> decltype(a+b) {...} // C++11
template <class T1, class T2>
auto max(const T1& a, const T2& b) { ... } // C++14
template <class T>
void f(T t);
template <class T>
void g(T& t);
int x = 42;
const int cx = 42;
const int& rx = cx;
f(x); // What is T?
f(cx); // What is T?
f(rx); // What is T?
g(x); // What is T?
g(cx); // What is T?
g(rx); // What is T?
template <class T>
void f(T t);
f(x); // x is int, T is int
f(cx); // cx is const int, T is int (const dropped)
f(rx); // rx is const int&, T is const int (reference dropped!)
template <class T>
void g(T& t);
g(x); // x is int, T is int
g(cx); // cx is const int, T is const int (const kept)
g(rx); // rx is const int&, T is const int (reference dropped)
During template type deduction:
inline int max(const int&a const int& b) { ... }
template <class T>
inline T max(const T& a, const T& b) { ... }
template <class T>
inline T max(const T& a, const T& b, const T& c) { ... }
For each of the following statemnts, which function is called?
::max(2, 5, 65);
::max(7., 20.);
::max('a', ' b');
::max(7, 42);
::max<>(7, 42);
::max<double>(7, 42);
::max('a', 42.7);
inline int max(const int&a const int& b) { ... }
template <class T>
inline T max(const T& a, const T& b) { ... }
template <class T>
inline T max(const T& a, const T& b, const T& c) { ... }
::max(2, 5, 65); // calls the template overload taking three arguments
::max(7., 20.); // calls max<double> (argument deduction)
::max('a', ' b'); // calls max<char> (argument deduction)
::max(7, 42); // Calls the non template overload
::max<>(7, 42); // Calls max<int> (argument deduction)
::max<double>(7, 42); // Calls max<double> (no argument deduction)
::max('a', 42.7); // Calls the non template overload
When calling a function with many overloads:
template <class T>
class vector
{
public:
// C++98: typedef T& reference
// C++11:
using reference = T&
vector();
vector(const vector& rhs);
reference operator[](size_t i);
void push_back(const T& t);
private:
T* p_data;
size_t m_size;
};
template <class T>
vector<T>::vector() { // ... }
template <class T>
vector<T>::vector(const vector& rhs) { // ... }
template <class T>
typename vector<T>::reference vector<T>::operator[](size_t i)
{
// ...
}
template <class T>
auto std::vector<T>::operator[](size_t i) -> reference
{
}
template <class T>
void f(vector<T>& v)
{
// Typename required:
using reference = typename vector<T>::reference;
// no need for typename:
using double_reference = vector<double>::reference;
}
int main()
{
vector<double> my_vec;
f(my_vec);
return 0;
}
template <class T>
class my_class
{
public:
template<class U>
void my_func(const U& u);
};
template <class T>
template <class U>
void my_class<T>::my_func(const U& u) { ... }
template <class T1, class T2>
class my_class
{
public:
void my_method(const T1& t1, const T2& t2);
};
// Partial specialization
template <class T>
class my_class<double, T>
{
public:
void my_method(double t1, const T& t2);
};
template <class T>
void myclass<double, T>::my_method(double t1, const T& t2) { ... }
// Full specialization
template <>
class my_class<int, double>
{
public:
void my_method(int t1, double t2);
};
// template<> // no template when defining members of full specialization
void my_class<int, double>::my_method(int t1, double t2) { ... }
template <class T1, class T2>
void my_func(T1, T2) { ... }
// OK:
template <>
void my_func<int, double>(int, double) { ... }
// Error:
template <class T>
void my_func<double, T>(double, T) { ... }
Function templates support full specialization only
In autodiff.hpp, define a template class "variable" that:
What is the issue with the previous design?
template <class E1, class E2>
class binary_add { // ... };
template <class F, class E1, class E2>
class binary_opĀ { // ... };
template <class E1, class E2>
binary_op<binary_add<E1, E2>, E1, E2>
operator+(const E1& e1, const E2& e2)
{
return binary_op<binary_add<E1, E2>, E1, E2>(e1, e2);
}
Passing E1 and E2 to binary_add is cumbersome
template <class E1, class E2>
class binary_add { // ... };
template <template <class, class> class F, class E1, class E2>
class binary_op
{
public:
using functor_type = F<E1, E2>
// ....
};
template <class E1, class E2>
binary_op<binary_add, E1, E2>
operator+(const E1& e1, const E2& e2)
{
return binary_op<binary_add, E1, E2>(e1, e2);
}
Traits are
the standard header type_traits defines a lot of them (see documentation on cppreference.com)
Implementation pattern:
template <class T>
struct add_reference
{
using reference = T&
};
// Specialization of add_reference for reference types
template <class T>
struct add_reference<T&>
{
using reference = T&
};
template <class T>
using add_reference_t = typename add_reference<T>::type;
template <class T>
class my_vector
{
using reference = add_reference_t<T>;
};
We combine them to get the best of two approaches
We want to store the variables by references and the operation structure by value.
What is the issue with this design?
template <class... T>
class universal_op
{
public:
using operands_types = std::tuple<T...>;
};
universal_op<int, double, float> u; // parameter pack is (int, double, float)
universal_op<int> u; // parameter pack is (int)
universal_op<> u; // OK, parameter pack is empty
template <class... T, class E1>
class invalid {};
The parameter pack must be the last template parameter
template <class... T>
void my_func(T... args)
{
call_func(args...);
}
template <class... T>
void my_other_func(const T&... args)
{
call_other_func(args...);
}
What if I want to pass some arguments by reference and other by values?
template <class T>
void my_func(T&& t) { ... }
int x = 42;
int& rx = x;
const int& cx = x;
// For the following calls, what is T? what is the type of the argument of my_func?
my_func(x);
my_func(rx);
my_func(cx);
my_func(std::move(x));
my_func(42);
During template type deduction with universal references:
template <class T>
void my_func(T&& t) { ... }
int x = 42;
int& rx = x;
const int& cx = x;
my_func(x); // T is int& - param type is int&
my_func(rx); // T is int& - param type is int&
my_func(cx); // T is const int& - param type is const int&
my_func(std::move(x)); // T is const int&& - param type is const int&&
my_func(42); // T is int - param type is int&&
template <class T>
void my_func(T&& t); // universal reference
tempate <class T>
void my_func(std::vector<T>&& v); // rvalue reference
Universal reference only for deduced types
template <class T>
void my_func_impl(T&& t) { ... }
template <class T>
void my_func(T&& t)
{
// calling my_func_impl with lvalue, wrong if t was an rvalue reference,
my_func_impl(t);
// calling my_func_impl with rvalue, wrong if t was an lvalue
my_func_impl(std::move(t));
}
template <class T>
void my_func(T&& t)
{
my_func_impl(std::forward<T>(t));
}
std::forward allows to achieve perfect forwarding
template <class... T>
void my_func(T&&... args)
{
// Forwards each argument, i.e. keeps the
// lvalue-ness or rvalue-ness of each argument
// indepently from other arguments
call_func(std::forward<T>(args)...);
}
template <class... T>
class universal_op;
template <class T>
class variable;
template <class E>
universal_op<exp, E> exp(const E& e)
{
return universal_op<exp, E>(e);
}
exp catches any type wihle we would like to catch universal_op and variables only
template <class D>
class expression
{
public:
D& operator()() { return *static_cast<D*>(this); }
const D& operator()() const { return *static_cast<const D*>(this); }
};
template <class T>
class variable : public expression<variable<T>>
{ // ... };
template <class... T>
class universal_op : public expression<universal_op<T...>>
{ // ... };
template <class E>
universal_op<exp, E> exp(const expression<E>& e)
{
return universal_op<exp, E>(e());
}
CRTP implements static polymorphism