Commit afbf8fac authored by GILLES Sebastien's avatar GILLES Sebastien
Browse files

Rewrite slightly the constexpr section, and add the metaprogramming Fibonacci...

Rewrite slightly the constexpr section, and add the metaprogramming Fibonacci implementation in the adequate notebook.
parent fadeee32
%% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Procedural programming](./0-main.ipynb) - [Static and constexpr](./7-StaticAndConstexpr.ipynb)
%% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Static-keyword" data-toc-modified-id="Static-keyword-1">Static keyword</a></span></li><li><span><a href="#Constexpr" data-toc-modified-id="Constexpr-2">Constexpr</a></span></li></ul></div>
%% Cell type:markdown id: tags:
## Static keyword
%% Cell type:markdown id: tags:
`static` may be seen as _at compile time_, whereas `dynamic` may be seen as _at runtime_.
A reply to this [StackOverflow question](https://stackoverflow.com/questions/572547/what-does-static-mean-in-c) gives a rather good summary of what `static` means in C (we shall see [later](./2-ObjectProgramming/5-static.ipynb) a C++-only keyword that unfortunately share the same moniker):
* Static defined local variables do not lose their value between function calls. In other words they are global variables, but scoped to the local function they are defined in.
* Static global variables are not visible outside of the C file they are defined in.
* Static functions are not visible outside of the C file they are defined in.
Only the first one is really relevant in C++, as for quantities that should be accessible only in the current file C++ provides a concept of his own: the [**unnamed namespace**](../6-InRealEnvironment/5-Namespace.ipynb#Unnamed-namespace).
Let's see the remaining case in action:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void FunctionWithStatic()
{
static int n = 0; // This initialisation occurs only at first call
// But `n` is not destroyed when the end bracket is reached and remains available
// in subsequent calls. However, `n` is available only inside this function.
std::cout << "The function has been called " << ++n << " times." << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
for (int i = 0; i < 5; ++i)
FunctionWithStatic();
}
```
%% Cell type:markdown id: tags:
It might be used for instance if you need to initialize something on the very first call of a function:
%% Cell type:code id: tags:
``` C++17
// Pseudo-code
void FunctionWithStatic()
{
static bool is_first_call = true;
if (is_first_call)
{
// Init stuff here on first call only, for instance something that requires heavy computation.
// ...
is_first_call = false;
}
// ... code executed at each call
}
```
%% Cell type:markdown id: tags:
## Constexpr
Let's consider the STL container `std::array`, which is the container of choice to store an array which size is known *at compile time* (containers will be seen more in depth in a [future notebook](./notebooks/5-UsefulConceptsAndSTL/3-Containers.ipynb)).
We've seen the allocation of an array on the stack follows this syntax:
%% Cell type:code id: tags:
``` C++17
#include <array>
std::array<double, 3ul> my_array;
int array[5ul];
```
%% Cell type:markdown id: tags:
where the size of the array is available at compile time (if not you have to use an array allocated on the heap at runtime).
Now imagine we want to init such an array with a size that results from a computation; let's say a Fibonacci series:
%% Cell type:code id: tags:
``` C++17
// Recursive function - Xeus cling may not appreciate if you call this cell several times.
auto fibonacci (std::size_t n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
return fibonacci(n-1) + fibonacci(n-2);
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
std::cout << fibonacci(5) << std::endl;
std::cout << fibonacci(10) << std::endl;
```
%% Cell type:code id: tags:
``` C++17
#include <array>
std::array<double, fibonacci(4)> my_fibonacci_4_array; // COMPILATION ERROR!
double array[fibonacci(5)]; // COMPILATION ERROR!
```
%% Cell type:markdown id: tags:
This doesn't seem outlandish: a computation is involved and this computation happens at runtime - even if in fact all the required elements to perform it were available at runtime (there were for instance no argument read from command line involved).
In C++ 03, you had two choices to resolve this
- Using a macro...
- Or using [metaprogramming](./4-Templates/4-Metaprogramming.ipynb) which is a tad overkill... (and involves boilerplate code).
- Or using [metaprogramming](../4-Templates/4-Metaprogramming.ipynb) which is a tad overkill... (and involves boilerplate code).
C++ introduced the keyword `constexpr`, which indicates something happens at compile time. It might be used for just a variable, or for more complicated construct such as functions or classes. The only requirement is that all the subparts used are themselves `constexpr`.
%% Cell type:code id: tags:
``` C++17
constexpr auto fibonacci_const (std::size_t n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
return fibonacci_const(n-1) + fibonacci_const(n-2);
}
```
%% Cell type:code id: tags:
``` C++17
#include <array>
constexpr auto fibonacci_index = 4ul;
std::array<double, fibonacci_const(fibonacci_index)> my_fibonacci_4_array;
double array[fibonacci_const(5)]; // Ok!
```
%% Cell type:markdown id: tags:
`constexpr` function may also be used as runtime function, but in this case their results can't of course be used at compile time.
%% Cell type:code id: tags:
``` C++17
#include <iostream>
int i = 7;
++i; // i is by no stretch a compile time variable!
std::cout << fibonacci_const(7) << std::endl; // ok!
std::cout << fibonacci_const(7) << std::endl;
```
%% Cell type:markdown id: tags:
`constexpr` becomes increasingly powerful over time:
- The function `fibonacci_const` above does not in fact work before C++ 14: this version of the standard introduced the possibility to provide several `return` in a `constexpr` function.
- `constexpr` is added wherever possible in the STL. This is still a work in progress: if you look for instance the [CppReference page](https://en.cppreference.com/w/cpp/algorithm/count) for `std::count` algorithm, you will see this algorithm becomes `constexpr` in C++ 20.
We will see another use of `constexpr` in a [later notebook](../4-Templates/2-Specialization.ipynb#If-constexpr).
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2021_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Metaprogramming](./4-Metaprogramming.ipynb)
%% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Introduction" data-toc-modified-id="Introduction-1">Introduction</a></span></li><li><span><a href="#Example:-same-action-upon-a-collection-of-heterogenous-objets" data-toc-modified-id="Example:-same-action-upon-a-collection-of-heterogenous-objets-2">Example: same action upon a collection of heterogenous objets</a></span></li></ul></div>
%% Cell type:markdown id: tags:
## Introduction
We will no go very far in this direction: metaprogramming is really a very rich subset of C++ of its own, and one that is especially tricky to code.
You can be a very skilled C++ developer and never use this; however it opens some really interesting prospects that can't be achieved easily (or at all...) without it.
I recommand the reading of \cite{Alexandrescu2001} to get the gist of it: even if it relies upon older versions of C++ (and therefore some of its hand-made constructs are now in one form or another in modern C++ or STL) it is very insightful to understand the reasoning behind metaprogrammation.
## Example: same action upon a collection of heterogenous objets
Let's say we want to put together in a same container multiple objects of heterogeneous type (one concrete case for which I have used that: reading an input data file with each entry is handled differently by a dedicated object).
In C++11, `std::tuple` was introduced for that purpose:
%% Cell type:code id: tags:
``` C++17
#include <tuple>
std::tuple<int, std::string, double, float, long> tuple =
std::make_tuple(5, "hello", 5., 3.2f, -35l);
```
%% Cell type:markdown id: tags:
What if we want to apply the same treatment of all of the entries? Let's roll with just printing each of them:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
std::cout << std::get<0>(tuple) << std::endl;
std::cout << std::get<1>(tuple) << std::endl;
std::cout << std::get<2>(tuple) << std::endl;
std::cout << std::get<3>(tuple) << std::endl;
std::cout << std::get<4>(tuple) << std::endl;
}
```
%% Cell type:markdown id: tags:
The treatment was rather simple here, but the code was duplicated manually for each of them. **Metaprogramming** is the art of making the compiler generate by itself the whole code.
The syntax relies heavily on templates, and those of you familiar with functional programming will feel at ease here:
%% Cell type:code id: tags:
``` C++17
#include <iostream>
template <std::size_t IndexT, std::size_t TupleSizeT>
struct PrintTuple
{
template<class TupleT> // I'm lazy I won't put there std::tuple<int, std::string, double, float, long> again...
static void Do(const TupleT& tuple)
{
std::cout << std::get<IndexT>(tuple) << std::endl;
PrintTuple<IndexT + 1ul, TupleSizeT>::Do(tuple); // that's the catch: call recursively the next one!
}
};
```
%% Cell type:markdown id: tags:
You may see the gist of it, but there is still an issue: the recursivity goes to the infinity... (don't worry your compiler will yell before that!). So you need a specialization to stop it - you may see now why I used a class template and not a function!
%% Cell type:code id: tags:
``` C++17
template <std::size_t TupleSizeT>
struct PrintTuple<TupleSizeT, TupleSizeT>
{
template<class TupleT>
static void Do(const TupleT& tuple)
{
// Do nothing!
}
};
```
%% Cell type:markdown id: tags:
With that, the code is properly generated:
%% Cell type:code id: tags:
``` C++17
{
std::tuple<int, std::string, double, float, long> tuple =
std::make_tuple(5, "hello", 5., 3.2f, -35l);
PrintTuple<0, std::tuple_size<decltype(tuple)>::value>::Do(tuple);
}
```
%% Cell type:markdown id: tags:
Of course, the call is not yet very easy: it's cumbersome to have to explicitly reach the tuple size... But as often in C++ an extra level of indirection may lift this issue:
%% Cell type:code id: tags:
``` C++17
template<class TupleT>
void PrintTupleWrapper(const TupleT& t)
{
PrintTuple<0, std::tuple_size<TupleT>::value>::Do(t);
}
```
%% Cell type:markdown id: tags:
And then the call may be simply:
%% Cell type:code id: tags:
``` C++17
{
std::tuple<int, std::string, double, float, long> tuple = std::make_tuple(5, "hello", 5., 3.2f, -35l);
PrintTupleWrapper(tuple);
}
```
%% Cell type:markdown id: tags:
In fact, my laziness earlier when I used a template argument rather than the exact tuple type pays now as this function may be used with any tuple (or more precisely with any tuple for which all elements comply with `operator<<`):
%% Cell type:code id: tags:
``` C++17
{
int a = 5;
std::tuple<std::string, int*> tuple = std::make_tuple("Hello", &a);
PrintTupleWrapper(tuple);
}
```
%% Cell type:markdown id: tags:
# Bonus: metaprogramming Fibonacci
In [notebook about constexpr](../1-ProceduralProgramming/7-StaticAndConstexpr.ipynb), I said implementing Fibonacci series before C++ 11 involved metaprogramming; here is an implementation (much more wordy than the `constexpr` one):
%% Cell type:code id: tags:
``` C++17
template<std::size_t N>
struct Fibonacci
{
static std::size_t Do()
{
return Fibonacci<N-1>::Do() + Fibonacci<N-2>::Do();
}
};
```
%% Cell type:code id: tags:
``` C++17
// Don't forget the specialization for 0 and 1!
template<>
struct Fibonacci<0ul>
{
static std::size_t Do()
{
return 0ul;
}
};
template<>
struct Fibonacci<1ul>
{
static std::size_t Do()
{
return 1ul;
}
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
std::cout << Fibonacci<5ul>::Do() << std::endl;
std::cout << Fibonacci<10ul>::Do() << std::endl;
```
%% Cell type:markdown id: tags:
And if the syntax doesn't suit you... you could always add an extra level of indirection to remove the `::Do()` part:
%% Cell type:code id: tags:
``` C++17
template<std::size_t N>
std::size_t FibonacciWrapper()
{
return Fibonacci<N>::Do();
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
std::cout << FibonacciWrapper<5ul>() << std::endl;
std::cout << FibonacciWrapper<10ul>() << std::endl;
```
%% Cell type:markdown id: tags:
As you can see, in some cases `constexpr` really alleviates some tedious boilerplate...
It should be noticed that although these computations really occur at compile time, they aren't nonetheless recognized automatically as `constexpr`:
%% Cell type:code id: tags:
``` C++17
constexpr auto fibo_5 = FibonacciWrapper<5ul>(); // COMPILATION ERROR!
```
%% Cell type:markdown id: tags:
To fix that, you need to declare `constexpr`:
- Each of the `Do` static method (`static constexpr std::size_t Do()`)
- The `FibonacciWrapper` function (`template<std::size_t N> constexpr std::size_t FibonacciWrapper()`)
So in this specific case you should really go with the much less wordy and more expressive expression with `constexpr` given in [aforementioned notebook](../1-ProceduralProgramming/7-StaticAndConstexpr.ipynb)
%% Cell type:markdown id: tags:
# References
[<a id="cit-Alexandrescu2001" href="#call-Alexandrescu2001">Alexandrescu2001</a>] Andrei Alexandrescu, ``_Modern C++ Design: Generic Programming and Design Patterns applied_'', 01 2001.
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2021_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment