Skip to content
Snippets Groups Projects
Commit 5baa8068 authored by Marcus Mohr's avatar Marcus Mohr
Browse files

Add some comments, fix some typos, add an explicit cast

parent 93fa73ba
No related branches found
No related tags found
No related merge requests found
%% Cell type:markdown id:9e611ffc-67dc-4bf7-ad80-747680965827 tags:
# Assorted other Examples
%% Cell type:markdown id:1bb13e1a-64a8-4a7b-9ac9-08189ed15574 tags:
Templates form one of the main sublanguages of C++ (Scott Meyers, "Effective C++", 2009).
We have, by far, not covered all aspects of this. The following diagram list the main areas Rainer Grimm intends to deal with in his current Templates part of
<a href="https://www.grimm-jaud.de/index.php/blog">Modernes C++</a>:
<img src="../images/Topics_in_Templates.png" width="90%">
Topics marked in yellow, are the ones we already talked about. Those in light red and blue are ones where either some important aspects are still missing, or that we have not covered, yet.
The plan for this notebook is to take a brief look at these.
%% Cell type:markdown id:d3377acf-83a4-480d-bee6-4e0520f4d476 tags:
## Template Metaprogramming
Quoting from Wikipedia:
> <a href="https://en.wikipedia.org/wiki/Metaprogramming">**Metaprogramming**</a> is a programming technique in which computer programs have the ability to treat other programs as their data. It means that a program can be designed to read, generate, analyze or transform other programs, and even modify itself while running.
> <a href="https://en.wikipedia.org/wiki/Template_metaprogramming">Template Metaprogramming</a> is a metaprogramming technique in which templates are used by a compiler to generate temporary source code, which is merged by the compiler with the rest of the source code and then compiled. The output of these templates can include compile-time constants, data structures, and complete functions. The use of templates can be thought of as compile-time polymorphism.
Examine the example below taken from Jürgen Lemke, "C++-Metaprogrammierung", Springer, 2016.
%% Cell type:code id:1584a4ef-cafe-44e5-8818-016f3d5e2003 tags:
``` C++17
%%file factorial.cpp
#include <iostream>
// general struct
template <unsigned int i>
struct TFactorial {
enum { eValue = i * TFactorial<i-1>::eValue };
};
// struct for base case
template <>
struct TFactorial<0> {
enum { eValue = 1 };
};
int main( void ) {
std::cout << "factorial of 5 is "
<< TFactorial<5>::eValue
<< std::endl;
return 0;
}
```
%% Output
Writing factorial.cpp
%% Cell type:code id:ff10a24d-af46-4662-8fe1-4ed814778dbc tags:
``` C++17
!g++ factorial.cpp; ./a.out
```
%% Output
factorial of 5 is 120
%% Cell type:markdown id:3e8bbe24 tags:
The catch here is that the complete computation of 5! is actually performed by the compiler. When we run the executable only the result is printed to the screen. With enough effort we could also make that a part of the compilation step.
%% Cell type:markdown id:ff86bb78 tags:
## Template Template Parameters
So far our template parameters were either
- type parameters (standard datatypes, classes, ...)
- non-type parameters (e.g. integer types)
However, we can also use class templates as template parameters. These are refered to as
**template template parameters**.
Before we examine the details of this. Let us start with an example where we might want to use this.
Assume that we work with finite element functions and have the following template classes for two standard FE spaces:
%% Cell type:code id:c4fae9a9 tags:
``` C++17
#include <iostream>
template< typename ValueType >
class P1Function {
public:
void info() const {
std::cout << "Finite Element Function from P1 Lagrange Space"
<< " (ValueType = " << typeid( ValueType ).name()
<< ")" << std::endl;
}
};
template< typename ValueType >
class P2Function {
public:
void info() const {
std::cout << "Finite Element Function from P2 Lagrange Space"
<< " (ValueType = " << typeid( ValueType ).name()
<< ")" << std::endl;
}
};
```
%% Cell type:markdown id:63cd43d6 tags:
The `ValueType` parameter will allow us to instantiate FE functions using different datatypes for their degrees of freedom.
- We might e.g. use double or float in mixed precision programming.
- Use `unsigned long int` for enumerating the DoFs when assembling a matrix-vector representation of the FE problem.
Now assume we want to write an operator class representing an operator that maps an FE function onto another FE function from the same space. We want the operator to be a template class and could use our standard approach for this:
%% Cell type:code id:e8b9fb8a tags:
``` C++17
template< typename FunctionType >
class OperatorV1 {
public:
void apply( const FunctionType& src, FunctionType& dst ) {
std::cout << "src: ";
src.info();
std::cout << "dst: ";
dst.info();
}
};
```
%% Cell type:code id:0483e979 tags:
``` C++17
int main() {
P2Function< double > src, dst;
OperatorV1< P2Function< double > > laplacian;
std::cout << "-> applying laplacian" << std::endl;
laplacian.apply( src, dst );
OperatorV1< std::string > lazyFail; // remember: implicit instantiation is lazy
}
main();
```
%% Output
-> applying laplacian
src: Finite Element Function from P2 Lagrange Space (ValueType = d)
dst: Finite Element Function from P2 Lagrange Space (ValueType = d)
%% Cell type:markdown id:60732d5b-c097-4fcb-9e2b-6debac9e6d34 tags:
The approach works, of course. However, it has some properties that might not be optimal in all settings:
1. We have note expressed that our operator class is intended to work with certain template arguments, only. ($\rightarrow$ see lazyFail above and *Concepts* in C++20).
1. There is no direct way to derive from the argument provided for the `FunctionType` template parameter the fact that it is an instance of template class `P2Function` (at least in C++17 and to the best of my knowledge :-). What if we need to generate in the operator an auxillary function of another datatype, e.g. `P2Function<int>`?
An approach that resolves these issues is to use a **template template parameter**:
%% Cell type:code id:f9b9cb47 tags:
``` C++17
template< template < typename > class func_t >
class OperatorV2 {
public:
OperatorV2() {
func_t< double > aux1;
func_t< int > aux2;
std::cout << "aux1: "; aux1.info();
std::cout << "aux2: "; aux2.info();
}
template< typename ValueType >
void apply( const func_t< ValueType >& src, func_t< ValueType >& dst )
{
std::cout << "src: ";
src.info();
std::cout << "dst: ";
dst.info();
}
};
```
%% Cell type:code id:1b69d421 tags:
``` C++17
int main() {
std::cout << "\n-> instantiating oper" << std::endl;
OperatorV2< P1Function > oper;
std::cout << "\n-> applying oper" << std::endl;
P1Function< float > srcP1, dstP1;
oper.apply( srcP1, dstP1 ); // instantiates OperatorV2::apply for float
}
main();
```
%% Output
-> instantiating oper
aux1: Finite Element Function from P1 Lagrange Space (ValueType = d)
aux2: Finite Element Function from P1 Lagrange Space (ValueType = i)
-> applying oper
src: Finite Element Function from P1 Lagrange Space (ValueType = f)
dst: Finite Element Function from P1 Lagrange Space (ValueType = f)
%% Cell type:markdown id:053c49a5 tags:
Note that the example also demonstrates the use of a **template member function**, `OperatorV2::apply()`.
---
If we are only interested in retaining the information on the template class, we can also use an approach with two template parameters:
%% Cell type:code id:7f98cebb tags:
``` C++17
template< template < typename > class func_t, typename value_t >
class OperatorV3 {
public:
OperatorV3() {
func_t< value_t > aux1;
func_t< int > aux2;
std::cout << "aux1: "; aux1.info();
std::cout << "aux2: "; aux2.info();
}
void apply( const func_t< value_t >& src, func_t< value_t >& dst )
{
std::cout << "src: ";
src.info();
std::cout << "dst: ";
dst.info();
}
};
```
%% Cell type:code id:dd6a1f5d tags:
``` C++17
int main() {
std::cout << "\n-> instantiating mass oper" << std::endl;
// OperatorV3< P1Function< float > > mass;
OperatorV3< P1Function, float > mass;
std::cout << "\n-> applying mass oper" << std::endl;
P1Function< float > src, dst;
mass.apply( src, dst );
}
main();
```
%% Output
-> instantiating mass oper
aux1: Finite Element Function from P1 Lagrange Space (ValueType = f)
aux2: Finite Element Function from P1 Lagrange Space (ValueType = i)
-> applying mass oper
src: Finite Element Function from P1 Lagrange Space (ValueType = f)
dst: Finite Element Function from P1 Lagrange Space (ValueType = f)
%% Cell type:markdown id:80018e40-0bbe-4949-b352-f1467e829df9 tags:
## Constexpr If
`if constexpr` was introduced in C++17 and consititutes a form of *conditional compilation* in the context of templates. Let us directly plunge into a first example:
%% Cell type:code id:4d05e6d4-b370-40a6-a5ad-334bbe8ed3be tags:
``` C++17
%%file demo.cpp
#include <iostream>
class A {
public:
void sayHello() { std::cout << "Class A says 'Hello'" << std::endl;
}
};
class B {
public:
void gruss() { std::cout << "Guten Morgen!" << std::endl;
}
};
template< typename T >
void checkMessage( T& obj ) {
if ( std::is_same_v< A, T > ) {
obj.sayHello();
}
else if( std::is_same_v< B, T > ) {
obj.gruss();
}
}
int main() {
A objTypeA;
checkMessage( objTypeA );
}
```
%% Output
Writing demo.cpp
%% Cell type:code id:00c57191-2b46-4181-ad72-4595ac882d03 tags:
``` C++17
!g++ -std=c++17 demo.cpp
```
%% Output
demo.cpp: In instantiation of ‘void checkMessage(T&) [with T = A]’:
demo.cpp:28:26: required from here
demo.cpp:22:9: error: ‘class A’ has no member named ‘gruss’
obj.gruss();
~~~~^~~~~
%% Cell type:markdown id:451b033c-a240-4b1d-b94b-5b0d7dc75d90 tags:
The *constexpr if* allows us to resolve this issue:
%% Cell type:code id:6be129fa tags:
``` C++17
%%file demo.cpp
#include <iostream>
class A {
public:
void sayHello() { std::cout << "Class A says 'Hello'" << std::endl;
}
};
class B {
public:
void gruss() { std::cout << "Guten Morgen!" << std::endl;
}
};
template< typename T >
void checkMessage( T& obj ) {
if constexpr( std::is_same_v< A, T > ) { // <- here is the important change!
obj.sayHello();
}
else if( std::is_same_v< B, T > ) {
obj.gruss();
// double a; a->foo();
}
}
int main() {
A objTypeA;
checkMessage( objTypeA );
B objTypeB;
checkMessage( objTypeB );
}
```
%% Output
Overwriting demo.cpp
%% Cell type:code id:1ac9c0ba tags:
``` C++17
!g++ -std=c++17 demo.cpp
```
%% Cell type:code id:083cc1e4 tags:
``` C++17
!./a.out
```
%% Output
Class A says 'Hello'
Guten Morgen!
%% Cell type:markdown id:c99ff76a tags:
What is the difference?
The condition in an *constexpr if* must be in general be a <a href="https://en.cppreference.com/w/cpp/language/constant_expression#Converted_constant_expression">contextually converted constant expression of type bool</a>.
In our template example it is something the compiler can check at instantiation! In this case the branch for which condition evaluates to *true* is instantiated, the other not! This is what makes the difference.
Note that the *false* branch, while not being instantiated, must still be syntactically correct. Remember the **two-phase lookup** we discussed. Not being instantiated means the second phase is skipped, but the code still needs to pass the first phase!
Thus, this is not equivalent to conditional compilation with preprocessing directives. As can be seen from the following example from cppreference.com:
%% Cell type:code id:ed62c962 tags:
``` C++17
void f() {
if constexpr(false) {
int i = 0;
int *p = i; // Error even though in discarded statement
}
}
```
%% Output
input_line_15:4:14: error: cannot initialize a variable of type 'int *' with an lvalue of type 'int'
int *p = i; // Error even though in discarded statement
^ ~
Interpreter Error:
%% Cell type:markdown id:bd46e337 tags:
---
A maybe more realistic example for the use of *constexpr if* is the following from the <a href="https://blog.tartanllama.xyz/if-constexpr/">blog</a> by Sy Brand. Note that this also features automatic return type deduction:
%% Cell type:code id:c273e25c-652a-461d-b351-3123afccbc11 tags:
``` C++17
#include <type_traits>
template <typename T>
auto get_value(T t) {
if constexpr (std::is_pointer_v<T>)
return *t;
else
return t;
}
```
%% Cell type:markdown id:3c1ba823-1b7e-4d7e-be29-59b0c1782579 tags:
Now let us test-drive this:
%% Cell type:code id:df64d78d-eecb-408d-a9db-1b1a72716de2 tags:
``` C++17
#include <iostream>
int main() {
int a = 2;
int* p = &a;
std::cout << "direct access ..... a = " << get_value( a ) << std::endl;
std::cout << "indirect access ... a = " << get_value( p ) << std::endl;
}
main();
```
%% Output
direct access ..... a = 2
indirect access ... a = 2
%% Cell type:markdown id:25c99ad6 tags:
When we inspect the instantiations with <a href="https://cppinsights.io/">cppinsights</a> we see that indeed only the valid branch gets instantiated:
<center><img src="../images/cppInsights02.png" width="70%"></center>
%% Cell type:markdown id:396d5d0e tags:
The *constexpr if* allows to simplify templates by reducing the need for specialisations, like in the following example using template meta-programming:
%% Cell type:code id:4f01ee73 tags:
``` C++17
%%file power.cpp
#define CONST_EXPR
#ifndef CONST_EXPR
template< int base, unsigned int exp >
struct Power {
static int value() {
return base * Power< base, exp - 1u >::value();
}
};
template< int base >
struct Power< base, 0 > {
static int value() {
return 1;
}
};
#else
template< int base, unsigned int exp >
struct Power {
static int value() {
if constexpr( exp > 0 ) {
return base * Power< base, exp - 1u >::value();
}
else {
return 1;
}
}
};
#endif
#include <iostream>
int main() {
std::cout << "5^3 = " << Power<5,3>::value() << std::endl;
}
```
%% Output
Overwriting power.cpp
%% Cell type:code id:8a4bb240 tags:
``` C++17
!g++ -std=c++17 power.cpp
```
%% Cell type:code id:39268b6a tags:
``` C++17
!./a.out
```
%% Output
5^3 = 125
%% Cell type:markdown id:b52ae491 tags:
## Dependent Names Revisited
We had encountered the concept of *non-dependent* and <a href="https://en.cppreference.com/w/cpp/language/dependent_name">*dependent names*</a> in notebook # 12, when we looked at inheritance of template classes and their member functions.
In the context of dependent names the following keywords might be needed:
* `typename`
* `template`
Assume we have the following template class:
%% Cell type:code id:8995872a tags:
``` C++17
template< typename T >
class A {
public:
using ptrType = T*;
};
```
%% Cell type:markdown id:60809792 tags:
Now we write a template function that internally needs to generate a pointer
%% Cell type:code id:f19c19b9 tags:
``` C++17
template< typename T >
void doSomething( T& obj ) {
A<T>::ptrType valPtr;
}
```
%% Output
input_line_21:3:18: error: expected ';' after expression
A<T>::ptrType valPtr;
^
;
input_line_21:3:19: error: use of undeclared identifier 'valPtr'
A<T>::ptrType valPtr;
^
Interpreter Error:
%% Cell type:markdown id:fef65d8e tags:
Hmm, what is the problem here? Let's try something else:
%% Cell type:code id:975025b2 tags:
``` C++17
template< typename T >
class foo {
using vType = T::valueType;
};
```
%% Output
input_line_22:3:17: error: missing 'typename' prior to dependent type name 'T::valueType'
using vType = T::valueType;
^~~~~~~~~~~~
typename
Interpreter Error:
%% Cell type:markdown id:fc965154 tags:
In both cases we use a name that is dependent on the template parameter `T`
* `A<T>::ptrType`
* `T::valueType`
The full details of this are tricky, but basically we need to help the compiler to understand that we are using a type name, by using the `typename` keyword.
From cppreference.com:
> In a declaration or a definition of a template, including alias template, a name that is not a member of the current instantiation and is dependent on a template parameter is not considered to be a type unless the keyword typename is used or unless it was already established as a type name, e.g. with a typedef declaration or by being used to name a base class.
That explains the error messages from the first example. `A<T>::ptrType` is not seen as a type name, but treated as a member of the template class `A<T>`.
Indeed, adding the `typename` keyword fixes the problem:
%% Cell type:code id:afb191b0 tags:
``` C++17
template< typename T >
void doSomething( T& obj ) {
typename A<T>::ptrType valPtr;
}
```
%% Cell type:markdown id:12c0fa43 tags:
A similar ambiguity problem can occur with template names:
> Similarly, in a template definition, a dependent name that is not a member of the current instantiation is not considered to be a template name unless the disambiguation keyword template is used or unless it was already established as a template name.
Assume we have class with a templated member function
```c++
class B {
public:
template< typename T >
void run() {};
};
```
Now we write a template function that internally generates an object, its type being the template parameter `T`, and calls its templated `run()`member function.
%% Cell type:code id:695e3775 tags:
``` C++17
template< typename T >
void func() {
T obj;
obj.run< T >();
}
```
%% Output
input_line_24:4:7: error: use 'template' keyword to treat 'run' as a dependent template name
obj.run< T >();
^
template
Interpreter Error:
%% Cell type:code id:da772d07 tags:
``` C++17
template< typename T >
void func() {
T obj;
obj.template run< T >();
}
```
%% Cell type:markdown id:bb14f831 tags:
## CRTP: Curiously Recurring Template Pattern
%% Cell type:markdown id:0af04dfc tags:
The curiously recurring template patterns describes a technique, where a class B inherits from a template class A, using itself as template argument:
```c++
template< typename T >
class A {};
class B : public A< B > {};
```
A nice list of usage scenarios and examples for CRTP can be found on the webpages of <a href="http://www.vishalchovatiya.com/crtp-c-examples/">Vishal Chovatiya</a>. The first of these demonstrates static polymorphism:
%% Cell type:code id:fffce178 tags:
``` C++17
%%file crtp.cpp
#include <iostream>
template<typename specific_animal>
struct animal {
void who() { static_cast<specific_animal*>(this)->who(); }
};
struct dog : animal<dog> {
void who() { std::cout << "dog" << std::endl; }
};
struct cat : animal<cat> {
void who() { std::cout << "cat" << std::endl; }
};
template<typename specific_animal>
void who_am_i(animal<specific_animal> &animal) {
animal.who();
}
int main( void ) {
cat c;
who_am_i(c); // prints `cat`
dog d;
who_am_i(d); // prints `dog`
}
```
%% Output
Writing crtp.cpp
%% Cell type:code id:1e32d10d tags:
``` C++17
!g++ crtp.cpp
```
%% Cell type:code id:da65a685 tags:
``` C++17
!./a.out
```
%% Output
cat
dog
%% Cell type:markdown id:3d5e5fae tags:
The catch here is that the `animal` class delegates the actual implementation of the `who()` member function to its children. However, in constrast to dynamic polymorphism with virtual functions this is handled at compile-time and does not induces a run-time penalty.
The catch here is that the `animal` class delegates the actual implementation of the `who()` member function to its children. However, in contrast to dynamic polymorphism with virtual functions this is handled at compile-time and does not induces a run-time penalty.
%% Cell type:markdown id:34f3bf32 tags:
The final object counter example is taken from <a href="https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern#Object_counter">Wikipedia</a>:
%% Cell type:code id:fcf8be3c tags:
``` C++17
#include <iostream>
template <typename T>
struct counter
{
static inline int objects_created = 0;
static inline int objects_alive = 0;
counter()
{
++objects_created;
++objects_alive;
}
counter(const counter&)
{
++objects_created;
++objects_alive;
}
protected:
~counter() // objects should never be removed through pointers of this type
{
--objects_alive;
}
};
class X : counter<X>
{
// ...
};
class Y : counter<Y>
{
// ...
};
int main() {
Y objY;
for( int k = 0; k < 2; ++k ) {
X* ptr = new X;
Y v;
}
std::cout << "X created: " << counter<X>::objects_created << std::endl;
std::cout << "X alive: " << counter<X>::objects_alive << std::endl;
std::cout << "Y created: " << counter<Y>::objects_created << std::endl;
std::cout << "Y alive: " << counter<Y>::objects_alive << std::endl;
}
```
%% Cell type:markdown id:f3309e3c tags:
What will this print?
%% Cell type:code id:d761dd85 tags:
``` C++17
main();
```
%% Output
X created: 2
X alive: 2
Y created: 3
Y alive: 1
%% Cell type:code id:fc82d71b tags:
%% Cell type:markdown id:1aa4f375-6b97-4009-a3c4-82aa59a8b60f tags:
Note that the templatisation of the `counter` class is essential here, as this will give us one individual counter pair per child `X` and `Y`.
%% Cell type:code id:22449331-1de2-48f1-82cf-bd5666124408 tags:
``` C++17
```
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment