From 8d3011d366f3b40a70fc719d969630799b0608bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Gilles?= <sebastien.gilles@inria.fr> Date: Sun, 2 May 2021 10:31:35 +0200 Subject: [PATCH 1/2] Create a new notebook in procedural to discuss static and constexpr. The static part was lifted from the notebook in ibject programming in which it was originally introduced. --- 1-ProceduralProgramming/0-main.ipynb | 3 +- .../7-StaticAndConstexpr.ipynb | 304 ++++++++++++++++++ 2-ObjectProgramming/5-static.ipynb | 71 +--- 3 files changed, 308 insertions(+), 70 deletions(-) create mode 100644 1-ProceduralProgramming/7-StaticAndConstexpr.ipynb diff --git a/1-ProceduralProgramming/0-main.ipynb b/1-ProceduralProgramming/0-main.ipynb index 6cbe872..003c9ef 100644 --- a/1-ProceduralProgramming/0-main.ipynb +++ b/1-ProceduralProgramming/0-main.ipynb @@ -19,7 +19,8 @@ " * [TP 2](./4b-TP.ipynb)\n", "* [Dynamic allocation](./5-DynamicAllocation.ipynb)\n", "* [Input/output](./6-Streams.ipynb)\n", - " * [TP 3](./6b-TP.ipynb)" + " * [TP 3](./6b-TP.ipynb)\n", + "* [Static and constexpr](./7-StaticAndConstexpr.ipynb)" ] }, { diff --git a/1-ProceduralProgramming/7-StaticAndConstexpr.ipynb b/1-ProceduralProgramming/7-StaticAndConstexpr.ipynb new file mode 100644 index 0000000..24bd3c7 --- /dev/null +++ b/1-ProceduralProgramming/7-StaticAndConstexpr.ipynb @@ -0,0 +1,304 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# [Getting started in C++](/) - [Procedural programming](./0-main.ipynb) - [Static and constexpr](./7-StaticAndConstexpr.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "toc": true + }, + "source": [ + "<h1>Table of contents<span class=\"tocSkip\"></span></h1>\n", + "<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", + "metadata": {}, + "source": [ + "## Static keyword" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`static` may be seen as _at compile time_, whereas `dynamic` may be seen as _at runtime_.\n", + "\n", + "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):\n", + "\n", + "* 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.\n", + "* Static global variables are not visible outside of the C file they are defined in.\n", + "* Static functions are not visible outside of the C file they are defined in.\n", + "\n", + "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).\n", + "\n", + "Let's see the remaining case in action:\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "\n", + "void FunctionWithStatic()\n", + "{\n", + " static int n = 0; // This initialisation occurs only at first call\n", + " // But `n` is not destroyed when the end bracket is reached and remains available\n", + " // in subsequent calls. However, `n` is available only inside this function.\n", + " std::cout << \"The function has been called \" << ++n << \" times.\" << std::endl; \n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "{\n", + " for (int i = 0; i < 5; ++i)\n", + " FunctionWithStatic(); \n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It might be used for instance if you need to initialize something on the very first call of a function:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "// Pseudo-code\n", + "\n", + "void FunctionWithStatic()\n", + "{\n", + " static bool is_first_call = true;\n", + " \n", + " if (is_first_call)\n", + " {\n", + " // Init stuff here on first call only, for instance something that requires heavy computation.\n", + " // ... \n", + " is_first_call = false;\n", + " }\n", + " \n", + " // ... code executed at each call\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Constexpr\n", + "\n", + "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))." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#include <array>\n", + "\n", + "std::array<double, 3ul> my_array;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "// Recursive function - Xeus cling may not appreciate if you call this cell several times.\n", + "\n", + "auto fibonacci (std::size_t n) \n", + "{\n", + " if (n == 0)\n", + " return 0;\n", + " if (n == 1)\n", + " return 1;\n", + " \n", + " return fibonacci(n-1) + fibonacci(n-2);\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "std::cout << fibonacci(5) << std::endl;\n", + "std::cout << fibonacci(10) << std::endl;" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#include <array>\n", + "std::array<double, fibonacci(4)> my_fibonacci_4_array; // COMPILATION ERROR!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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).\n", + "\n", + "In C++ 03, you had two choices to resolve this\n", + "\n", + "- Using a macro...\n", + "- Or using [metaprogramming](./4-Templates/4-Metaprogramming.ipynb) which is a tad overkill... (and involves boilerplate code).\n", + "\n", + "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", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "constexpr auto fibonacci_const (std::size_t n) \n", + "{\n", + " if (n == 0)\n", + " return 0;\n", + " if (n == 1)\n", + " return 1;\n", + " \n", + " return fibonacci_const(n-1) + fibonacci_const(n-2);\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#include <array>\n", + "constexpr auto fibonacci_index = 4ul;\n", + "std::array<double, fibonacci_const(fibonacci_index)> my_fibonacci_4_array; " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`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", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "\n", + "int i = 7;\n", + "++i; // i is by no stretch a compile time variable!\n", + "\n", + "std::cout << fibonacci_const(7) << std::endl; // ok!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`constexpr` becomes increasingly powerful over time:\n", + "\n", + "- 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.\n", + "- `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." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "© _CNRS 2016_ - _Inria 2018-2021_ \n", + "_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/)_ \n", + "_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_\n", + "\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "C++17", + "language": "C++17", + "name": "xcpp17" + }, + "language_info": { + "codemirror_mode": "text/x-c++src", + "file_extension": ".cpp", + "mimetype": "text/x-c++src", + "name": "c++", + "version": "17" + }, + "latex_envs": { + "LaTeX_envs_menu_present": true, + "autoclose": false, + "autocomplete": true, + "bibliofile": "biblio.bib", + "cite_by": "apalike", + "current_citInitial": 1, + "eqLabelWithNumbers": true, + "eqNumInitial": 1, + "hotkeys": { + "equation": "Ctrl-E", + "itemize": "Ctrl-I" + }, + "labels_anchors": false, + "latex_user_defs": false, + "report_style_numbering": false, + "user_envs_cfg": false + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": false, + "sideBar": true, + "skip_h1_title": true, + "title_cell": "Table of contents", + "title_sidebar": "Contents", + "toc_cell": true, + "toc_position": {}, + "toc_section_display": true, + "toc_window_display": true + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/2-ObjectProgramming/5-static.ipynb b/2-ObjectProgramming/5-static.ipynb index 8393533..ae6b2ed 100644 --- a/2-ObjectProgramming/5-static.ipynb +++ b/2-ObjectProgramming/5-static.ipynb @@ -23,76 +23,9 @@ "source": [ "## Static in C\n", "\n", - "We haven't dealt yet with the keyword `static`, which exists in C and could have been adressed in the procedural part of this lecture.\n", + "As a reminder, we have seen in a [previous notebook](./1-ProceduralProgramming/7-StaticAndConstexpr.ipynb#Static-keyword) the `static` keyword inherited from C. \n", "\n", - "As the C++ concept of **static method** uses up the same keyword and is not entirely related (even if both may be intertwined as we shall see in the [last section](./5-static.ipynb#Static-order-initialization-fiasco---and-its-fix)), we shall first review the C concept before studying the C++ one.\n", - "\n", - "First of all, `static` may be seen as _at compile time_, whereas `dynamic` may be seen as _at runtime_.\n", - "\n", - "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:\n", - "\n", - "* 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.\n", - "* Static global variables are not visible outside of the C file they are defined in.\n", - "* Static functions are not visible outside of the C file they are defined in.\n", - "\n", - "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).\n", - "\n", - "Let's see the remaining case in action:\n", - "\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#include <iostream>\n", - "\n", - "void FunctionWithStatic()\n", - "{\n", - " static int n = 0; // This initialisation occurs only at first call\n", - " // But `n` is not destroyed when the end bracket is reached and remains available\n", - " // in subsequent calls. However, `n` is available only inside this function.\n", - " std::cout << \"The function has been called \" << ++n << \" times.\" << std::endl; \n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "{\n", - " for (int i = 0; i < 3; ++i)\n", - " FunctionWithStatic(); \n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "It might be used for instance if you need to initialize something on the very first call of a function:\n", - "\n", - "````\n", - "void FunctionWithStatic()\n", - "{\n", - " static bool is_first_call = true;\n", - " \n", - " if (is_first_call)\n", - " {\n", - " // Init stuff here on first call only\n", - " ... \n", - " is_first_call = false;\n", - " }\n", - " \n", - " ... // code executed at each call\n", - "}\n", - "\n", - "````" + "What follows is the same keyword used in a very different context (even if the one we already know will pop up in an idion presented here).\n" ] }, { -- GitLab From 51aaa9f7a358cc34d9bb969752eb945230d54306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Gilles?= <sebastien.gilles@inria.fr> Date: Mon, 3 May 2021 10:05:55 +0200 Subject: [PATCH 2/2] Rewrite slightly the constexpr section, and add the metaprogramming Fibonacci implementation in the adequate notebook. --- .../7-StaticAndConstexpr.ipynb | 24 ++-- 4-Templates/4-Metaprogramming.ipynb | 136 ++++++++++++++++++ 2 files changed, 147 insertions(+), 13 deletions(-) diff --git a/1-ProceduralProgramming/7-StaticAndConstexpr.ipynb b/1-ProceduralProgramming/7-StaticAndConstexpr.ipynb index 24bd3c7..a38706a 100644 --- a/1-ProceduralProgramming/7-StaticAndConstexpr.ipynb +++ b/1-ProceduralProgramming/7-StaticAndConstexpr.ipynb @@ -108,7 +108,7 @@ "source": [ "## Constexpr\n", "\n", - "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:" ] }, { @@ -117,15 +117,15 @@ "metadata": {}, "outputs": [], "source": [ - "#include <array>\n", - "\n", - "std::array<double, 3ul> my_array;" + "int array[5ul];" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ + "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).\n", + "\n", "Now imagine we want to init such an array with a size that results from a computation; let's say a Fibonacci series:" ] }, @@ -165,8 +165,7 @@ "metadata": {}, "outputs": [], "source": [ - "#include <array>\n", - "std::array<double, fibonacci(4)> my_fibonacci_4_array; // COMPILATION ERROR!" + "double array[fibonacci(5)]; // COMPILATION ERROR!" ] }, { @@ -178,7 +177,7 @@ "In C++ 03, you had two choices to resolve this\n", "\n", "- Using a macro...\n", - "- Or using [metaprogramming](./4-Templates/4-Metaprogramming.ipynb) which is a tad overkill... (and involves boilerplate code).\n", + "- Or using [metaprogramming](../4-Templates/4-Metaprogramming.ipynb) which is a tad overkill... (and involves boilerplate code).\n", "\n", "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`." ] @@ -206,9 +205,7 @@ "metadata": {}, "outputs": [], "source": [ - "#include <array>\n", - "constexpr auto fibonacci_index = 4ul;\n", - "std::array<double, fibonacci_const(fibonacci_index)> my_fibonacci_4_array; " + "double array[fibonacci_const(5)]; // Ok!" ] }, { @@ -228,8 +225,7 @@ "\n", "int i = 7;\n", "++i; // i is by no stretch a compile time variable!\n", - "\n", - "std::cout << fibonacci_const(7) << std::endl; // ok!" + "std::cout << fibonacci_const(7) << std::endl;" ] }, { @@ -239,7 +235,9 @@ "`constexpr` becomes increasingly powerful over time:\n", "\n", "- 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.\n", - "- `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." + "- `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.\n", + "\n", + "We will see another use of `constexpr` in a [later notebook](../4-Templates/2-Specialization.ipynb#If-constexpr)." ] }, { diff --git a/4-Templates/4-Metaprogramming.ipynb b/4-Templates/4-Metaprogramming.ipynb index 5c426ac..6add317 100644 --- a/4-Templates/4-Metaprogramming.ipynb +++ b/4-Templates/4-Metaprogramming.ipynb @@ -209,6 +209,142 @@ "}" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Bonus: metaprogramming Fibonacci\n", + "\n", + "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):\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "template<std::size_t N>\n", + "struct Fibonacci\n", + "{\n", + " \n", + " static std::size_t Do()\n", + " {\n", + " return Fibonacci<N-1>::Do() + Fibonacci<N-2>::Do();\n", + " }\n", + " \n", + " \n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "// Don't forget the specialization for 0 and 1!\n", + "\n", + "template<>\n", + "struct Fibonacci<0ul>\n", + "{\n", + " \n", + " static std::size_t Do()\n", + " {\n", + " return 0ul;\n", + " }\n", + " \n", + " \n", + "};\n", + "\n", + "\n", + "template<>\n", + "struct Fibonacci<1ul>\n", + "{\n", + " \n", + " static std::size_t Do()\n", + " {\n", + " return 1ul;\n", + " }\n", + " \n", + " \n", + "};" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "\n", + "std::cout << Fibonacci<5ul>::Do() << std::endl;\n", + "std::cout << Fibonacci<10ul>::Do() << std::endl;" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "template<std::size_t N>\n", + "std::size_t FibonacciWrapper()\n", + "{\n", + " return Fibonacci<N>::Do();\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "\n", + "std::cout << FibonacciWrapper<5ul>() << std::endl;\n", + "std::cout << FibonacciWrapper<10ul>() << std::endl;\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see, in some cases `constexpr` really alleviates some tedious boilerplate... \n", + "\n", + "It should be noticed that although these computations really occur at compile time, they aren't nonetheless recognized automatically as `constexpr`: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "constexpr auto fibo_5 = FibonacciWrapper<5ul>(); // COMPILATION ERROR!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To fix that, you need to declare `constexpr`:\n", + "- Each of the `Do` static method (`static constexpr std::size_t Do()`)\n", + "- The `FibonacciWrapper` function (`template<std::size_t N> constexpr std::size_t FibonacciWrapper()`)\n", + "\n", + "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", "metadata": {}, -- GitLab