Commit 21ba2717 authored by Praetorius, Simon's avatar Praetorius, Simon
Browse files

lecture notes and presentation slides updated for generic programming, iterators and functors

parent 650ec24a
\chapter{Generic programming}
If you have an algorithm or function that can be expressed independent of the representation details of the arguments and without complicated case-by-case
variations, it should be written in a generic form. Thereby, instead of implementing a concrete function (or class), just a template for the function (or class)
is provided that is later filled with concrete realizations of types. This is called \emph{generic programming}.
\begin{zitat}{Musser, David R.; Stepanov, Alexander A., Generic Programming (1989)}
Generic programming centers around the idea of abstracting from concrete, efficient algorithms to obtain generic algorithms that can be combined with
different data representations to produce a wide variety of useful software.
\end{zitat}
% =================================================================================================
\section{Templates}
Generic programming in C++ allows to parametrize functions and classes not just with values (function parameters, or constructor arguments), but with types.
A \emph{template parameter} thereby is a special kind of parameter passed to the function, class, or even variable. Inside of the realization of the function
or class, this type parameter, represented by its name, can be used as any other regular type, \eg to declare variables, to specify function parameter lists
or return types, or to parametrize other function or class templates.
An introductory example illustrates the usage of function and class templates:
\begin{minted}{c++}
#include <cmath>
// class template
template <typename T> // T is type for coordinates, e.g. T=double
struct MyPoint
{
using value_type = T; // introduction of a type alias
T x, y; // type T used here to declare a variable
// alternatively: value_type x,y;
};
// function template
template <typename P> // P is type for the vectors to calculate the distance of
auto distance(P const& a, P const& b) // type P in the argumentlist, both arguments
{ // should have the same type.
using T = typename P::value_type; // access to a class type
T dx = a.x - b.x;
T dy = a.y - b.y; // Or: auto dy = a.y - b.y
return std::sqrt(dx * dx + dy * dy);
}
\end{minted}
The call to the (generic) function \texttt{distance}, \ie the \emph{instantiation} of that function with a concrete type, is
initiated by passing concrete arguments to that function, or by specifying the type explicitly
\begin{minted}{c++}
int main()
{
// Type for MyPoint explicitly specified
MyPoint<double> a{1.0, 2.0}, b{7.0,-1.5};
// Or implicit deduction of the type (C++17)
MyPoint c{2.0, 3.0}; // double coordinates
MyPoint d{3.0f, 4.0f}; // float coordinates
// Type for the arguments of distance( , ) automatically deduced
std::cout << "Distance of a and b (double) = "
<< distance(a,b) << std::endl;
// Similarly, by explicit specification:
std::cout << "Distance of a and b (double) = "
<< distance<MyPoint<double>>(a,b) << std::endl;
// Call the function with different type
MyPoint<float> a2{1.0f, 2.0f}, b2{7.0f,-1.5f};
std::cout << "Distance of a and b (float) = "
<< distance(a2,b2) << std::endl;
// invalid argument types
distance(1.0, 2.0); // ERROR: `double` has no `value_type` and access to
// the members x and y not possible
// incompatible argument types MyPoint<double> and MyPoint<float>
distance(c, d); // ERROR
}
\end{minted}
% =================================================================================================
\section{Generic functions}
Generic functions extend the concept of function overloading. The generic functions are called \emph{function templates} (and sometimes also
\emph{templated functions}).
The basic idea is that instead of a function with concrete types, we parametrize the function with a type parameter that can be used
instead of the concrete type.
The syntax to declare a function template:
\begin{minted}{c++}
template <typename NAME1, typename NAME2, (...)> FUNCTION_DECLARATION;
template <class NAME, (...)> FUNCTION_DECLARATION;
\end{minted}
It simply starts with the keyword \texttt{template} and in angular brackets \texttt{<...>} a comma separated list of template parameters.
The argument \texttt{NAMEx} is called \emph{template parameter} and must be a valid name in C++. The argument is introduced with either the
additional keyword \texttt{typename} or \texttt{class} for type-parameters (there is no difference of these two keywords).
\begin{rem}
Also, \texttt{NAME} can be a \emph{nontype}-parameter, a \emph{template}-parameter, or a \emph{parameter-pack}. We will focus here on type parameters and later introduce nontype parameters.
\end{rem}
\begin{rem}
The number of template parameters is restricted only by compiler-specific limits.
\end{rem}
The call of a function template is a two-phase procedure:
\begin{enumerate}
\item Template instantiation $\rightarrow$ concrete function
\item Invocation of that concrete function
\end{enumerate}
\emph{Template instantiation} is the process of filling the placeholder template parameters with concrete types (or values, see below). This can be achieved
in two ways. 1. Explicit specification of the types:
%
\cppline{FUNCTION_NAME<TYPES...>(PARAMETERS);}
%
where \texttt{TYPES} must be replaced with concrete types in the order of the template parameter declaration (without the keywords \texttt{typename} or
\texttt{class}).
Or the template instantiation can be obtained by implicit deduction of the template parameter types from the function arguments
(\emph{argument type deduction} (ATD)):
%
\cppline{FUNCTION_NAME(PARAMETERS);}
%
Thereby, the function parameter must be dependent on all the template parameter types.
\begin{example}
We have the function template \texttt{abs\_difference} defined as follow:
\begin{minted}{c++}
template <typename T>
T abs_difference(T value1, T value2)
{
return std::abs(value1 - value2);
}
\end{minted}
Explicit instantiation of this function template:
\begin{minted}{c++}
abs_difference<double>(1.0, 2.0);
abs_difference<float>(1.0f, 2.0f);
\end{minted}
and implicit instantiation by argument type deduction:
\begin{minted}{c++}
abs_difference(1.0, 2.0); // -> T = double
abs_difference(3, 4); // -> T = int
\end{minted}
\end{example}
The type deduction thereby is very similar to the \cpp{auto} type deduction from the auto-placeholder syntax introduced before.
Actually, in C++ first the template parameter deduction was invented and later the auto-type deduction was added with \cxx{11}.
\begin{rem}
Argument type deduction only works if all the types can be deduced uniquely from the function parameters. In the example above, we have
declared a function template with two parameters and have specified both with the same template parameter \texttt{T}. Thus, we have to
pass two arguments of the same type to the function template in order to allow the template parameters to be deduced from the
function parameters:
%
\begin{minted}{c++}
double a = 1.0;
float b = 2.0f;
abs_difference(a,b); // ERROR: cannot deduce template parameters
\end{minted}
%
The parameter \texttt{T} could be either deduced to \cpp{double} or to \cpp{float}.
But, if the template is instantiated with an explicit type, no automatic deduction needs to be done and thus we have a regular
function that can be called with different argument that are automatically converted:
%
\begin{minted}{c++}
abs_difference<double>(a, b); // OK => generates function 'double(double,double)' and
// calls this function with parameters double and float
// -> implicit conversion of float to double
\end{minted}
\end{rem}
Since function templates can be ``called'' like regular functions, name and overload resolution must be considered. This will be discussed in detail
later. Here just an example:
\begin{minted}{c++}
// non-template function
double abs_difference(double a, double b); // (a)
// function template
template <class T>
T abs_difference(T a, T b); // (b)
double a=1.0, b=2.0;
abs_difference(a,b); // (1) calls (a)
abs_difference<double>(a,b); // (2) calls (b)
abs_difference<>(a,b); // (3) calls (b)
\end{minted}
If there is an exact match of a non-templated function, this is always preferred (1). If the explicit instantiation with empty template parameter list,
it asks for argument type deduction, but enforces a call to the instantiated template, instead of the non-template (3).
% -------------------------------------------------------------------------------------------------
\subsection{Templates as placeholders}
Similar to the \cpp{auto} placeholder, template parameter can be equipped with qualifiers, like \cpp{const}, reference (or pointer).
But additionally, template parameters can be used just like regular types, as parameters to other templates, like class templates:
%
\begin{minted}{c++}
// accept all std::vector arguments only
template <class T>
double norm1(std::vector<T> const& vec); // (a)
// accept any type, and pass it by reference
template <class T>
double norm2(T const& vec); // (b)
// accept any type, and pass it by value
template <class T>
double norm3(T vec); // (c)
\end{minted}
%
The rules how to deduce a type \texttt{T} from the arguments is just like the deduction rules of \cpp{auto}, plus a pattern matching
in case the parameter is used in another template.
%
\begin{minted}{c++}
std::vector<double> vec = {...};
norm1(vec); // T = double
norm2(vec); // T = std::vector<double>
norm3(vec); // T = std::vector<double>
\end{minted}
% =================================================================================================
\section{Generic Classes}
A generic class, or \emph{class template} (or templated class) is a user-defined type, parameterized with other types (and values). This
allows to specify the internal data-type during the \emph{instantiation} of the outer class type. Important examples of class templates
are containers, where the type of the elements that are stored in the container are passed as template parameters to the class template
(like \texttt{std::vector<double>}).
The syntax is similar to that of generic functions:
\begin{minted}{c++}
template <typename NAME1, typename NAME2, (...)> CLASS_DECLARATION;
template <class NAME1, (...)> CLASS_DECLARATION;
\end{minted}
We call this a \emph{template declaration} (or \emph{template definition} in case of class definition) with \texttt{NAMEx} the \emph{template parameters}.
\texttt{CLASS\_DECLARATION} is thereby either \cpp{struct CLASS_NAME} or \cpp{class CLASS_NAME} (similar to regular classes).
Again, there is no difference between \texttt{typename} and \texttt{class} of the template parameter declaration, for type parameters.
\begin{rem}
In analogy to function parameters, template parameters can have default ``values'' (or default types), specified starting from the right-most template
parameter:
\cppline{template <class T1, class T2, class T3 = int> class CLASS_NAME { ... };}
\end{rem}
The \emph{template instantiation} of a class template, \ie the replacement of a template parameter with a concrete type (or value), follows the syntax
\begin{minted}{c++}
CLASS_NAME<TYPES...> variable(CONSTRUCTOR_ARGUMENTS); // or...
CLASS_NAME<TYPES...> variable{CONSTRUCTOR_ARGUMENTS}; // Uniform initialization, or
// initializer lists
\end{minted}
where \texttt{TYPES...} is a list of concrete types (without the keyword \texttt{typename} or \texttt{class}).
\begin{rem}
With \marginpar{[\cxx{17}]}\cxx{17} the class template parameters can be automatically deduced from the class constructor arguments. This
is called \emph{class template argument deduction}. Similar to argument type deduction for function templates the template parameters
are deduced from the arguments passed to a constructor of the class template. This works if all class template parameters are related to
a constructor parameter:
%
\begin{minted}{c++}
std::pair p(2, 4.5); // deduces to std::pair<int, double> p(2, 4.5);
std::tuple t(4, 3, 2.5); // same as auto t = std::make_tuple(4, 3, 2.5);
std::vector v{1,2,3,4,5}; // deduces to std::vector<int> v{1,2,3,4,5};
\end{minted}
%
but
%
\begin{minted}{c++}
std::vector v2(7); // ERROR: type cannot be deduced. The constructor argument is
// just the size of the vector, no value.
\end{minted}
See also \href{https://en.cppreference.com/w/cpp/language/class_template_argument_deduction}{cppreference:class\_template\_argument\_deduction}.
\end{rem}
% =================================================================================================
\section{Non-type parameters}\label{sec:non_type_parameters}
Apart from type parameters, template parameters can represent integral values. Instead of specifying the template parameter with \texttt{typename} or
\texttt{class}, it is declared with any integral value type, \eg \cpp{int}.
The following example defines a class template of a container with fixed number of elements that is given as non-type template parameter:
\begin{minted}{c++}
template <class T, int N>
struct fixed_size_array
{
T& operator[](int i) { return data[i]; }
const T& operator[](int i) const { return data[i]; }
private:
T data[N]; // size is fixed by template parameter
};
int main()
{
fixed_size_array<double, 3> point3D;
point3D[2] = 7.0;
}
\end{minted}
\begin{rem}
Not all types are allowed as non-type template parameters. It must be possible to uniquely evaluate those types at compile-type. Therefore the C++
standard has restricted non-type template parameters to integral types, enums, characters, and pointer types (until C++17) and floating-point types and some literal class types (since C++20).
\end{rem}
Similar to type parameters, default values are possible:
\begin{minted}{c++}
template <class T, int N = 3>
struct fixed_size_array { ... };
\end{minted}
Since non-type parameters must be specified at compile-time to instantiate that template, it must be a \cpp{constexpr}. Runtime values are not allowed:
\begin{minted}{c++}
constexpr int n = 7;
fixed_size_array<double, n> point7D; // OK
int m = 5;
fixed_size_array<double, m> point5D; // ERROR: m is runtime value
\end{minted}
% =================================================================================================
\section{Comparison of templates to concrete types/functions}
A class generated from a class template is like any other regular / concrete class. It is just a user-defined data-type with size and alignment.
One can instantiate that class and create type aliases to that class. Especially,
\begin{itemize}
\item there is \underline{no} additional runtime overhead compared to a hand-written (concrete) class,
\item there is \underline{no} space overhead in the instantiated template compared to a hand-written class.
\end{itemize}
Each class template is translated into a regular class at template instantiation. Those instantiations get internally a unique name tepending on the
instantiated template parameters, \eg \cpp{mypoint<double>} $\Rightarrow$ \texttt{[...]mypointId[...]} (in g++)
After instantiation of a template, all its internal types, member functions, member variables and static constants are concrete, have a concrete type
or (in case of static constants) have a concrete value, that can be evaluated and asked for.
For function templates it is similar. After function template instantiation, the template becomes a regular function, that participates in overload
resolution like any other function. It can be passed as a function parameter to other functions, or can be stored in a function reference variable,
or a \texttt{std::function} object. And, similar to class templates,
\begin{itemize}
\item there is \underline{no} additional runtime overhead compared to a hand-written (concrete) function,
\item there is \underline{no} space overhead in the instantiated template compared to a hand-written function.
\end{itemize}
\chapter{Functors}
A Functor is an abstraction of a classical function, but implemented as class type. It can be used (called) like a regular function, but has additional
advantage to use all the flexibility of a class, like local member variables, or other member functions that can be called.
The basic idea is, that a class that implements the \cpp{operator()} can be evaluated like a function. The call to that special member function has the same
syntax as a regular function invocation.
Example:
\begin{minted}{c++}
struct Square {
// Implementation of the bracket operator
double operator()(double x) const {
return x*x;
}
};
int main() {
// Instantiation of the class
Square square{};
// Use the instance of the class, like a regular function
double y = square(3.0); // = 9
}
\end{minted}
\begin{guideline}{Guideline}
Implement functors as classes with an overloaded \cpp{operator()} as \cpp{public} and (often) \cpp{const} member-function.
\end{guideline}
% =================================================================================================
\section{Functors as function parameters}
Since functors are regular user-defined types, they can be passed to any other function as function parameter. This allows to write algorithms
without specifying the concrete operation explicitly in code, but expecting this operation to be passed as argument. Examples for this type
of algorithms are sorting algorithms, where your want to specify the comparison operator, \eg \cpp{std::less}, as argument so that you
can easily switch between increasing and decreasing order of the elements.
Another example is the function \cpp{for_each}, that applies to each element in a container a function (\eg to change the value of the element):
\begin{minted}{c++}
void for_each(std::vector<double>& vec, Square const& f) {
for (auto& v_i : vec)
v_i = f(v_i);
}
int main() {
std::vector<double> v{1.0, 2.0, 3.0, 4.0, 5.0};
// Instantiation of the class
Square square{};
// Pass the functor to the function
for_each(v, square);
// -> {1.0, 4.0, 9.0, 16.0, 25.0}
}
\end{minted}
%
We could even instantiate the functor directly in the call of the \cpp{for_each} function, \ie
\cppline{for_each(v, Square{});}
The problem with this approach is, that we might want to pass different functors to the same function / algorithm. This is a perfect example
of a function template. Instead of explicitly specifying the type of the functor that we can pass, we specify a template parameter for the functor
argument:
\begin{minted}{c++}
template <class F>
void for_each(std::vector<double>& vec, F const& f) {
for (auto& v_i : vec)
v_i = f(v_i);
}
\end{minted}
This allows to call the function \cpp{for_each} the same way as before (thanks to ATD), but we can also pass another functor without reimplementing the
algorithm.
\begin{rem}
A more generic implementation of the \cpp{for_each} function uses a second template parameter for the container type, \ie
%
\begin{minted}{c++}
template <class Range, class F>
void for_each(Range& range, F const& f) {
for (auto& element : range)
element = f(element);
}
\end{minted}
%
This allows to use that function with \cpp{std::vector<...>} of any type supported by the functor, or \cpp{std::list}, \cpp{std::array}, or
any other type that can be iterated over using range-based for-loops.
\end{rem}
% ==============================================================================
\section{Parametrized Functors}
We can not only parametrize the functions that use the functor, but also the functor itself. The allows to specify the argument or return types
of the \cpp{operator()} implemented inside the functor, or any other useful type that we need to implement the functor operation.
\begin{example}
The square functor from above could be applied to any number type that supports multiplication. Thus, we could make the implementation more
generic by parametrizing the functor:
\begin{minted}{c++}
template <typename domain_type, typename range_type = domain_type>
struct Square
{
range_type operator()(domain_type x) const {
return x*x;
}
};
\end{minted}
In the instantiation of the class, we now have to instantiate the functor class template as well:
\cppline{for_each(v, Square<double>{});}
\end{example}
Often, the functor class itself needs no parametrization, but the member function \cpp{operator()} depends on template parameters. So,
one could directly parametrize just the member function, instead of the whole class:
Example:
\begin{minted}{c++}
struct Square
{
template <typename domain_type>
auto operator()(domain_type x) const {
return x*x;
}
};
\end{minted}
%
For simplicity, I have used the return type \cpp{auto} to let the compiler automatically deduce the return type from the expression in the
function. The \cpp{auto} return type gets very useful when combined with input template parameters.
The call to \cpp{for_each} is now as simple as before:
\cppline{for_each(v, Square{});}
and we do not need to know the type of the argument in advance.
% =================================================================================================
\section{Lambda expressions}
In \marginpar{[\cxx{11}]}\cxx{11} the definition of functors was simplified a lot. Instead of writing a class or class template first, then instantiating that
class and passing it to an algorithm, one can define an ``anonymous'' functor directly in the line of usage. The compiler then generates automatically
a corresponding class with a unique names, instantiates that class and passes it to the function.
Those anonymous functors are called \emph{lambda expressions} in C++ and can be declared as follows:
%
\begin{minted}{c++}
[captures...] (parameters...) -> ReturnType { function body };
/* or shorted in case of empty parameter list */
[captures...] { function body };
\end{minted}
%
Without the \emph{capture} clause, it looks like a regular function definition.
\begin{itemize}
\item The \emph{captures} is a list of zero or more variables or references that can be used inside the function.
\item \emph{parameters} is a list of function parameters including its types and qualifiers (like in regular function declarations)
\item \emph{ReturnType} is the (optional) (trailing) return type of the function, that can depend on the types of the function parameters
It can be omitted, resulting in the automatic return type deduction, like in functions returning \cpp{auto}.
\item The function body may contain any sequence of C++ statements and (optionally) a return statement.
\end{itemize}
\begin{example}
The following example illustrates the usage of lambda expressions, compared to classical functors:
\begin{minted}{c++}
// functor with internal member variable to store data.
struct Scale {
double factor_;
Scale(double factor = 1.0)
: factor_(factor)
{}
double operator()(double value) const
{
return value * factor_;
}
};
int main() {
std::vector<double> v = {1.0, 2.0, 3.0, 4.0, 5.0};
double factor = 3.0;
// Instantiate a regular functor with constructor argument
for_each(v, Scale{factor});
// Use a lambda expression
for_each(v, [factor](double value) { return factor * value; });
}
\end{minted}
%
The variable \texttt{factor} is captured, and can thus be used inside the lambda expression.
\end{example}
The capture list is very similar to a list of constructor argument of the functor. All variables in that list introduce a local
member variable, that can be used inside the lambda expressions function body. Any other variable cannot be used there, since it
generates an unnamed functor class with an \cpp{operator()} member function that is called on evaluation.
Using the capture list, a connection to the surrounding scope can be established. The following values are possible in the capture list:
\begin{minted}{c++}
[] // no capture
[a] // copies the variable a into a local member variable of the functor
[&a] // stores a reference to the variable a
[=] // copies all variables of the surrounding scope that are used inside the function body
[&] // references all variables of the surrounding scope ...
\end{minted}
\subsection{Generic lambdas}
Like functions and classes, also lambda expressions can be made generic. In the example \texttt{Square} above, we have parametrized the
inner member function \cpp{operator()} with the type of the arguments. A similar templated bracket operator for lambda expressions
can be obtained, by using the placeholder \cpp{auto} inside the function parameter list:
%
\begin{minted}{c++}
for_each(v, [factor](auto value) { return factor * value; });
\end{minted}
%
This introduces a member-function template with an unnamed template parameter, \ie it is similar to the functor definition
%
\begin{minted}{c++}
template <class T>
struct Scale {
T factor_;
Scale(T factor = T(1))
: factor_(factor)
{}
template <typename S>
auto operator()(S value) const
{
return value * factor_;
}
};
\end{minted}
%
but, we do not have a name for the type of the parameter like \texttt{S} in the explicit functor definition.
\begin{rem}
The type of the function parameters in generic lambdas can be obtained by using the \cpp{decltype} operator.
\end{rem}
Similar to classical templates and the \cpp{auto} placeholder in variable declarations, the function parameters in generic lambdas
can be qualified with \cpp{const}, reference and pointer. But they cannot be used as placeholders in other templated types, \eg
\cpp{std::vector<auto>} is not allowed as function parameter in generic lambdas.
\begin{rem}
With \marginpar{[\cxx{20}]}\cxx{20} one can introduce named template parameters in generic lambdas. Therefore, one adds a declaration
of template parameters after the capture block in the lambda definition:
%
\begin{minted}{c++}
[...] <class P1, class P2, (...)> (P1 param1, P2 param2...) -> ReturnType { function body };
\end{minted}
\end{rem}
% =================================================================================================
\section{Abbreviated function template}
Since \marginpar{[\cxx{20}]}\cxx{20}, a similar syntax as used for generic lambda can be used for regular function. When placeholder types \cpp{auto} appear in the
parameter list of a function declaration or of a function template declaration, the declaration declares a function template, and one
invented template parameter for each placeholder is appended to the template parameter list.
%
\begin{minted}{c++}
void f1(auto arg); // same as template<class T> void f1(T arg)
void f2(auto const& arg); // same as template<class T> void f2(T const& arg)
\end{minted}
%
This is especially useful if the actual type of the argument is irrelevant but the algorithm allows to generically pass arguments of different
type.
This diff is collapsed.
......@@ -83,9 +83,9 @@ There are exercises every week to practice the C++ programming. During the semes
\input{06_arrays}
\input{07_namespaces}
\input{08_classes}
%\input{09_generics}
%\input{10_functors}
%\input{11_iterators}
\input{09_generics}
\input{10_functors}
\input{11_iterators}
%\input{12_expression_templates}
%\input{13_template_specialization}
%\input{14_metaprogramming}
......
......@@ -484,13 +484,14 @@ template <class Mat, class Vec, class Iteration>
int cg (Mat const& A, Vec& x, Vec const& b, Iteration& iter)
{
using Scalar = typename Vec::value_type;
using Real = typename Iteration::real_type;
Scalar rho{0}, rho_1{0}, alpha{0};
Vec p{b}, q{b}, z{b};
Vec r{b - A*x};
Vec r{b};
A.mv(-1.0, x, r); // r = b - A*x