diff --git a/1-ProceduralProgramming/1-Variables.ipynb b/1-ProceduralProgramming/1-Variables.ipynb index 9ee6928dafb61af1bd4059f24a62756961423a38..fd3087019f15f6d08fe3da90fe2c25720019ddc6 100644 --- a/1-ProceduralProgramming/1-Variables.ipynb +++ b/1-ProceduralProgramming/1-Variables.ipynb @@ -67,10 +67,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Although not mandatory, it is **strongly** recommended to give a\n", + "Although not mandatory, it is **strongly** recommended to give an\n", "initial value to your variables, as an expression between brackets. \n", "\n", - "Not providing an initial value may lead to unexpected behaviour. For instance you can't make hypothesis upon the values of `number` and `real` in the cell above: you might end-up with any value... and someone else might get other values on his computer!\n", + "Not providing an initial value may lead to unexpected behaviour. For instance you can't make hypothesis upon the values of `number` and `real` in the cell above: you might end-up with any value... and someone else might get other values on their computer!\n", "\n", "\n", "If you give braces without values, a predefined and associated value\n", @@ -97,7 +97,7 @@ "C++ actually supports many other historical forms\n", "of initialization, which you will encounter everywhere, including in this tutorial,\n", "with brackets and/or an equal sign. There are some subtle differences\n", - "between each other... that you can ignore most of the time." + "between each other... that you can ignore most of the time (still, if you're beginning our advice is to take the habit to use braces which are both less ambiguous and a bit more secure than the others syntaxes)." ] }, { @@ -481,7 +481,7 @@ "source": [ "## Pointers\n", "\n", - "A pointer contains the address of another variable. It declares itself by slipping\n", + "A pointer contains the address in memory of another variable. It declares itself by slipping\n", "a `*` character before the name. It can be initialized or not with the address\n", "of another variable. To explicitly extract the address of this other variable,\n", "we use the symbol `&`.\n", @@ -796,12 +796,16 @@ " int a { 2 }, b { 3 };\n", " const int* p { &a }; // Address pointed to is modifiable, but not the underlying value.\n", " \n", - " std::cout << \"Value pointed by p (which doesn't allow value modification) is: \" << *p << std::endl;\n", + " std::cout << \"Value pointed by pointer p (which doesn't allow value modification) is: \" << *p << std::endl;\n", " \n", " int* p2 {&a}; // Pointer to the same memory area, but no constness here.\n", " *p2 = 10;\n", " \n", - " std::cout << \"Value pointed by p (and modified through p2) is: \" << *p << std::endl; \n", + " std::cout << \"Value pointed by pointer p (and modified through p2) is: \" << *p << std::endl; \n", + " \n", + " a = -3; \n", + " std::cout << \"Value pointed by pointer p (and modified through variable directly) is: \" << *p << std::endl; \n", + "\n", "}" ] }, @@ -884,7 +888,7 @@ "#include <iostream>\n", "\n", "{\n", - " int k[2][3] { { 5, 7, 0 }, { 3, 8, 9 }};\n", + " int k[2][3] { { 5, 7, 0 }, { 3, 8, 9 } };\n", " \n", " std::cout << \"k[0][0] = \" << k[0][0] << \" (expected: 5)\" << std::endl;\n", " std::cout << \"k[1][2] = \" << k[1][2] << \" (expected: 9)\" << std::endl;\n", diff --git a/1-ProceduralProgramming/2-Conditions-and-loops.ipynb b/1-ProceduralProgramming/2-Conditions-and-loops.ipynb index 18f61ae451dc50c1359ee98f32b2de32b5b80f81..21028ecedfdaa05f84289705e4cba1f76a842ee9 100644 --- a/1-ProceduralProgramming/2-Conditions-and-loops.ipynb +++ b/1-ProceduralProgramming/2-Conditions-and-loops.ipynb @@ -115,8 +115,7 @@ " }\n", " \n", " std::cout << \"a was not modified and is still 2: \" << a << std::endl; \n", - "}\n", - "\n" + "}" ] }, { @@ -332,7 +331,7 @@ "* The variable is an integer, an enum (see [next notebook](./3-Types.ipynb#Enumerations)) or might be convertible into one of those.\n", "* The relationship considered is an equality.\n", "\n", - "I present it quickly in [appendix](../7-Appendix/Switch.ipynb) but we do not have yet seen all the elements needed to explain its interest (which remains fairly limited compared to the powerful `switch` in other languages...)\n" + "I present it quickly in [appendix](../7-Appendix/Switch.ipynb) but we do not have yet seen all the elements needed to explain its interest (which remains fairly limited compared to the vastly more powerful `switch` in other languages...)\n" ] }, { @@ -428,9 +427,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The evaluation of the logical operators is performed from the left to the right.\n", + "Builtin operators `&&` and `||` perform short-circuit evaluation: they do not evaluate the second operand if the result is known after evaluating the first. \n", "\n", - "Builtin operators `&&` and `||` perform short-circuit evaluation: they do not evaluate the second operand if the result is known after evaluating the first. Please notice this means these operators aren't commutative!" + "Please notice this means these operators are **not commutative**!" ] }, { @@ -450,6 +449,15 @@ "std::cout << \"b was not incremented!: \" << b << std::endl;" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "The story is different if the ordering of the conditions to test is inverted:" + ] + }, { "cell_type": "code", "execution_count": null, @@ -467,7 +475,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Please notice it's true only for built-in operators: later in this tutorial we will deal with operators overloading, and such operators always evaluate both operands." + "Please notice it's true **only for built-in operators**: later in this tutorial we will deal with operators overloading, and such operators always evaluate both operands." ] }, { @@ -789,7 +797,7 @@ "source": [ "but honestly in more complex cases `break` can help keep the code more readable. \n", "\n", - "`continue` is related: it is useful when in some conditions you want to skip the remainder of the current loop iteration to go directly to the next:" + "`continue` is related: it is useful when in some conditions you want to skip the rest of the current loop iteration to go directly to the next:" ] }, { @@ -822,14 +830,14 @@ " \n", " for (int j = 2; j < i / 2; ++j)\n", " {\n", - " if (i % j == 0)\n", + " if (i % j == 0) // % returns the remainder of the division\n", " {\n", " is_prime = false;\n", " break; // this break cuts the inner loop 'for (int j = 1; j < i / 2; ++j)'\n", " }\n", " }\n", "\n", - " std::cout << (is_prime ? \" and prime.\" : \".\") << std::endl;\n", + " std::cout << (is_prime ? \" and prime.\" : \".\") << std::endl; // the ternary operator\n", " }\n", "}" ] diff --git a/1-ProceduralProgramming/3-Types.ipynb b/1-ProceduralProgramming/3-Types.ipynb index 69d5c07c2aea7636efef310a3774265f3673532a..fab40c48ff3a7e650542f7b304108d13d269a859 100644 --- a/1-ProceduralProgramming/3-Types.ipynb +++ b/1-ProceduralProgramming/3-Types.ipynb @@ -141,7 +141,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "A shortcoming of historical `enum ` is that the same word can't be used in two different `enum`:" + "A (huge) shortcoming of historical `enum ` is that the same word can't be used in two different `enum`:" ] }, { @@ -282,14 +282,14 @@ "\n", "The _0 notation column_ is the way to notice explicitly the type in an expression; of course any value might be used instead of 0. A `u` might be used to signal the unsigned status for integer types; for instance `3ul` means 3 as an _unsigned long_. `auto` notation below will illustrate a case in which such a notation is useful.\n", "\n", - "The STL features rather heavily a type named `std::size_t`, which by design is able to store the maximum size of a theoretically possible object of any type (including array). On most (all?) systems `std::size_t` is an alias to an `unsigned long`. More may be found about this type on [CppReference](https://en.cppreference.com/w/cpp/types/size_t). \n", + "The STL features rather heavily a type named `std::size_t`, which by design is able to store the maximum size of a theoretically possible object of any type (including array). On most (all?) systems `std::size_t` is an alias to an `unsigned long`. More may be found about this type on [CppReference](https://en.cppreference.com/w/cpp/types/size_t). The equivalent counterpart for signed integers is the [`std::ptrdiff_t`](https://en.cppreference.com/w/cpp/types/ptrdiff_t), which is the signed integer type of the result of subtracting two pointers. \n", "\n", "You might also encounter [`std::ptrdiff_t`](https://en.cppreference.com/w/cpp/types/ptrdiff_t), which is akin to `std::size_t` but for *signed* values. It is typically used in the STL to store the result of subtracting two pointers. \n", "\n", "\n", "#### Numeric limits\n", "\n", - "Always keep in mind the types of the computer don't match the abstract concept you may use in mathematics... See [link](https://en.cppreference.com/w/cpp/types/numeric_limits). The types stored especially don't go from minus infinity to infinity:" + "Always keep in mind the types of the computer don't match the abstract concept you may use in mathematics... The types stored especially don't go from minus infinity to infinity:" ] }, { @@ -330,6 +330,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "(see the [CppReference dedicated page](https://en.cppreference.com/w/cpp/types/numeric_limits) for more details about `std::numeric_limits`).\n", + "\n", "If an initial value is not in the range, the compiler will yell:" ] }, @@ -367,7 +369,7 @@ " unsigned int max = std::numeric_limits<unsigned int>::max();\n", " \n", " std::cout << \"Max = \" << max << std::endl;\n", - " std::cout << \"Max + 1 = \" << max + 1 << \"!\" << std::endl;\n", + " std::cout << \"Max + 1 = \" << max + 1 << \" // !\" << std::endl;\n", "}" ] }, @@ -381,7 +383,7 @@ "\n", "The most obvious way to avoid this is to choose appropriate types: if your integer might be huge a `long` is more appropriate than an `int`.\n", "\n", - "Other languages such as Python gets a underlying integer model that is resilient to this kind of issue but there is a cost behind; types such as those used in C++ are tailored to favor optimization on your hardware." + "Other languages such as Python gets a underlying integer model that is resilient to this kind of issue but there is a cost behind it; types such as those used in C++ are tailored to favor optimization on your hardware." ] }, { @@ -817,7 +819,9 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [], "source": [ "#include <vector>\n", diff --git a/1-ProceduralProgramming/4-Functions.ipynb b/1-ProceduralProgramming/4-Functions.ipynb index 0fffcf0133a30dd3277bf8a350d3e328ed08c984..19a94d783f2b88a793d602bfd674bfb4bcaca98f 100644 --- a/1-ProceduralProgramming/4-Functions.ipynb +++ b/1-ProceduralProgramming/4-Functions.ipynb @@ -21,13 +21,74 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Function definition\n", + "## Function declaration and definition\n", "\n", - "To be usable in a C++ instruction, a function must be __defined__ beforehand. This definition includes the name and type of the arguments, the type of the return value and the instruction block that make up the function.\n", + "### Function declaration\n", "\n", - "`void` is a special type to indicate a function doesn't return any value.\n", + "The aim of a **function declaration** is just to describe its prototype:\n", "\n", - "__BEWARE__: Functions can't be defined in blocks." + "- The return value of the function (or `void` if the function returns nothing).\n", + "- The number, type and ordering of the parameters (if any). Naming them specifically is entirely optional (but is useful if you choose to put your [Doxygen](../6-InRealEnvironment/6-Tools.ipynb#Doxygen) documentation along your functions declarations).\n", + "\n", + "Declaration ends by a semicolon `;`; the point of the declaration is to announce to the rest of the code that a function with this exact prototype exists and may be used elsewhere. \n", + "\n", + "Few examples of function declarations:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "int ComputeMinimum(int a, int b);" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "void DoStuff(double); // naming the parameter is optional" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "int ReturnFive(); // providing a parameter is also optional...\n", + " // Don't bother Xeus-cling warning: it IS here a function declaration!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Due to the use of notebooks we won't need much proper function declaration, but we will revisit this [much later](../6-InRealEnvironment/2-FileStructure.ipynb) when we will see how a real project is written with different files involved." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Function definition" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "On the other hand, the **function definition** aims at providing the implementation of the function that may (and should!) have been declared beforehand.\n", + "\n", + "In a function definition:\n", + "- No semicolon after the prototype\n", + "- A block follows the prototype instead; inside this block the implementation is written.\n", + "- Parameter may not be named, but if they are used (which should be the case most of the time hopefully...) you will need such a name in the implementation.\n", + "\n", + "For instance:" ] }, { @@ -38,30 +99,59 @@ "source": [ "#include <iostream>\n", "\n", - "void PrintDivision(int arg1, int arg2)\n", + "void PrintDivision(int numerator, int denominator) // no semicolon here!\n", "{\n", - " if (arg2 == 0)\n", + " if (denominator == 0)\n", " std::cout << \"Failure: division by zero!\" << std::endl;\n", " else\n", " {\n", " int division ;\n", - " division = arg1/arg2 ;\n", - " std::cout << arg1 << \" / \" << arg2 << \" = \" << division << std::endl ; \n", + " division = numerator / denominator ;\n", + " std::cout << numerator << \" / \" << numerator << \" = \" << division << std::endl ; \n", " }\n", "}" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "and when the function is invoked at some point, the implementation above is direcly put in motion:" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "{\n", - " PrintDivision(12, 3);\n", - " PrintDivision(6, 0);\n", - " PrintDivision(15, 6);\n", - "}" + "int num = 3;\n", + "int denom = 2;\n", + "\n", + "PrintDivision(num, denom);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### A terminology note: _parameter_ and _argument_\n", + "\n", + "In the function above, I called `numerator` and `denominator` **parameters**, and you may also have heard the term **argument**.\n", + "\n", + "For the purists:\n", + "\n", + "- **parameter** is the name used when speaking about what is between the parenthesis during the function definition (`numerator` and `denominator` in the function definition)\n", + "- **argument** is what is passed when the function is effectively called within your code (`num` and `denom` in the above cell)\n", + "\n", + "I don't guarantee I am using the right term everywhere in the code: I'm not a purist and often use one for another (if you want to remember properly a helpful mnemotechnic is that **a**rguments are **a**ctual).\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Functions can't be nested, or declared within blocks" ] }, { @@ -77,9 +167,9 @@ "metadata": {}, "outputs": [], "source": [ - "void function_1() // a function might have no arguments\n", + "void Function1() // a function might have no arguments\n", "{\n", - " void subfunction() // COMPILATION ERROR!\n", + " void Subfunction() // COMPILATION ERROR!\n", " {\n", " \n", " } \n", @@ -110,7 +200,7 @@ "source": [ "#include <iostream>\n", "\n", - "void increment_and_print(int i)\n", + "void IncrementAndPrint(int i)\n", "{\n", " ++i;\n", " std::cout << \"Inside the function: i = \" << i << std::endl;\n", @@ -119,7 +209,7 @@ "{\n", " int i = 5; // I could have named it differently - it doesn't matter as the scope is different!\n", "\n", - " increment_and_print(i);\n", + " IncrementAndPrint(i);\n", "\n", " std::cout << \"Outside the function: i = \" << i << std::endl; \n", "}" @@ -129,7 +219,30 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The `i` in the block body and in the function definition is not the same: one or the other could have been named differently and the result would have been the same." + "The `i` in the block body and in the function definition are not the same: one or the other could have been named differently and the result would have been the same:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "\n", + "void IncrementAndPrint2(int local_argument)\n", + "{\n", + " ++local_argument;\n", + " std::cout << \"Inside the function: local_argument = \" << local_argument << std::endl;\n", + "}\n", + "\n", + "{\n", + " int i = 5; // I could have named it differently - it doesn't matter as the scope is different!\n", + "\n", + " IncrementAndPrint2(i);\n", + "\n", + " std::cout << \"Outside the function: i = \" << i << std::endl; \n", + "}" ] }, { @@ -138,7 +251,7 @@ "source": [ "### Passing arguments by reference\n", "\n", - "If we intended to modify the value of `i` outside the function, we should have passed it by reference:" + "If we intended to modify the value of `i` outside the function (and given the name of the function this is strongly hinted...), we should have passed it by reference:" ] }, { @@ -149,7 +262,7 @@ "source": [ "#include <iostream>\n", "\n", - "void increment_and_print_by_reference(int& i)\n", + "void IncrementAndPrintByReference(int& i)\n", "{\n", " ++i;\n", " std::cout << \"Inside the function: i = \" << i << std::endl;\n", @@ -158,7 +271,7 @@ "{\n", " int i = 5; // I could have named it differently - it doesn't matter as the scope is different!\n", "\n", - " increment_and_print_by_reference(i);\n", + " IncrementAndPrintByReference(i);\n", "\n", " std::cout << \"Outside the function: i = \" << i << std::endl; \n", "}" @@ -177,7 +290,7 @@ "metadata": {}, "outputs": [], "source": [ - "int compute_division(int arg1, int arg2, int& quotient, int& remainder)\n", + "int ComputeDivision(int arg1, int arg2, int& quotient, int& remainder)\n", "{\n", " if (arg2 == 0)\n", " return -1; // error code.\n", @@ -199,10 +312,10 @@ "{\n", " int quotient, remainder;\n", " \n", - " if (compute_division(5, 3, quotient, remainder) == 0)\n", + " if (ComputeDivision(5, 3, quotient, remainder) == 0)\n", " std::cout << \"5 / 3 = \" << quotient << \" with a remainder of \" << remainder << '.' << std::endl;\n", " \n", - " if (compute_division(5, 0, quotient, remainder) == 0)\n", + " if (ComputeDivision(5, 0, quotient, remainder) == 0)\n", " std::cout << \"5 / 0 = \" << quotient << \" with a remainder of \" << remainder << '.' << std::endl;\n", " else\n", " std::cout << \"Can't divide by 0!\" << std::endl;\n", @@ -217,7 +330,7 @@ "\n", "The function above gets two outputs: the quotient and the remainder of the euclidian division. Moreover, this function returns an error code: by convention this function returns 0 when everything is alright and -1 in case of a zero divider. \n", "\n", - "Using such an error code is a very common pattern in C, that might as well be used in C++... The issue is that it requires a lot of discipline from the user of the function: there are no actual incentive to use the return value! Just calling `compute_division()` as if it was a void function is perfectly fine (and yet completely ill-advised). We will see [later](../5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb) the `exception` mechanism C++ recommends instead of error codes.\n", + "Using such an error code is a very common pattern in C, that might as well be used in C++... The issue is that it requires a lot of discipline from the user of the function: there are no actual incentive to use the return value! Just calling `compute_division()` as if it was a void function is perfectly fine (and yet completely ill-advised). We will see [later](../5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb) the `exception` mechanism C++ recommends instead of error codes (and discuss a bit more error codes as well).\n", "\n", "Below is an example where things go awry due to the lack of check:" ] @@ -230,18 +343,18 @@ "source": [ "#include <iostream>\n", "\n", - "void print_division(int arg1, int arg2)\n", + "void PrintDivision(int arg1, int arg2)\n", "{\n", " int quotient, remainder;\n", " \n", - " compute_division(arg1, arg2, quotient, remainder); // the dev 'forgot' to check the error code.\n", + " ComputeDivision(arg1, arg2, quotient, remainder); // the dev 'forgot' to check the error code.\n", " \n", " std::cout << \"Euclidian division of \" << arg1 << \" by \" << arg2 << \" yields a quotient of \" \n", " << quotient << \" and a remainder of \" << remainder << std::endl; \n", "}\n", "\n", - "print_division(8, 5);\n", - "print_division(8, 0); // bug!\n" + "PrintDivision(8, 5);\n", + "PrintDivision(8, 0); // bug!\n" ] }, { @@ -249,17 +362,10 @@ "metadata": {}, "source": [ "The developer made two important mistakes:\n", - "* The return value of `compute_division` is not checked, so something is printed on screen.\n", + "* The return value of `ComputeDivision` is not checked, so something is printed on screen.\n", "* This is something completely out of control: quotient and remainder don't get a default value that would help to see if something is askew. The behaviour is undefined: you have no guarantee on the values the program will print (currently I see the same values as in the previous function call, but another compiler/architecture/etc... might yield another wrong value." ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**UPDATE:** I still do not advise to use error codes, but the [nodiscard](https://en.cppreference.com/w/cpp/language/attributes/nodiscard) attribute introduced in C++ 17 helps your compiler to warn you when the return value was left unused." - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -281,7 +387,7 @@ "source": [ "#include <iostream>\n", "\n", - "void increment_and_print_by_pointer(int* i)\n", + "void IncrementAndPrintByPointer(int* i)\n", "{\n", " *i += 1;\n", " std::cout << \"Inside the function: i = \" << *i << std::endl;\n", @@ -290,7 +396,7 @@ "{\n", " int i = 5; // I could have named it differently - it doesn't matter as the scope is different!\n", "\n", - " increment_and_print_by_pointer(&i);\n", + " IncrementAndPrintByPointer(&i);\n", "\n", " std::cout << \"Outside the function: i = \" << i << std::endl; \n", "}" @@ -316,7 +422,7 @@ "#include <iostream>\n", "\n", "//! \\brief Returns 1 if the value is positive, 0 if it is 0 and -1 if it's negative.\n", - "int sign(int a)\n", + "int Sign(int a)\n", "{\n", " if (a > 0)\n", " return 1;\n", @@ -329,7 +435,7 @@ "\n", "{\n", " for (int a = -3; a < 4; ++a)\n", - " std::cout << \"Sign of \" << a << \" = \" << sign(a) << std::endl;\n", + " std::cout << \"Sign of \" << a << \" = \" << Sign(a) << std::endl;\n", "}" ] }, @@ -349,7 +455,7 @@ "metadata": {}, "outputs": [], "source": [ - "auto sum(int a, int b) -> int // Currently doesn't compile in Xeus-cling environment\n", + "auto Sum(int a, int b) -> int // Currently doesn't compile in Xeus-cling environment\n", "{\n", " return a + b;\n", "}" @@ -368,7 +474,7 @@ "metadata": {}, "outputs": [], "source": [ - "auto sum(int a, int b) // compiles just fine in Xeus-cling\n", + "auto Sum(int a, int b) // compiles just fine in Xeus-cling\n", "{\n", " return a + b;\n", "}" @@ -385,7 +491,7 @@ "int a = 8;\n", "int b = -3;\n", "\n", - "std::cout << a << \" + \" << b << \" = \" << sum(a, b) << std::endl;" + "std::cout << a << \" + \" << b << \" = \" << Sum(a, b) << std::endl;" ] }, { @@ -406,15 +512,15 @@ "outputs": [], "source": [ "#include <string>\n", - "void f(); \n", + "void F(); \n", "\n", - "void f(int); // Ok\n", + "void F(int); // Ok\n", "\n", - "double f(int, double); // Ok\n", + "double F(int, double); // Ok\n", "\n", - "auto f(char) -> int; // Ok (alternate function syntax)\n", + "auto F(char) -> int; // Ok (alternate function syntax)\n", "\n", - "std::string f(double, int, char*); // Ok" + "std::string F(double, int, char*); // Ok" ] }, { @@ -432,9 +538,9 @@ "metadata": {}, "outputs": [], "source": [ - "void g(int);\n", + "void G(int);\n", "\n", - "int g(int); // COMPILATION ERROR" + "int G(int); // COMPILATION ERROR" ] }, { @@ -466,7 +572,7 @@ "source": [ "#include <iostream>\n", "\n", - "void h(double a)\n", + "void H(double a)\n", "{\n", " std::cout << \"h(double) is called with a = \" << a << '.' << std::endl;\n", "}\n" @@ -479,7 +585,7 @@ "outputs": [], "source": [ "#include <iostream>\n", - "void h(double* a) // Ok\n", + "void H(double* a) // Ok\n", "{\n", " std::cout << \"h(double*) is called with a = \" << *a << \"; a is doubled by the function.\" << std::endl;\n", " *a *= 2.;\n", @@ -493,7 +599,7 @@ "outputs": [], "source": [ "#include <iostream>\n", - "void h(double& a) // Ok... but not advised! (see below)\n", + "void H(double& a) // Ok... but not advised! (see below)\n", "{\n", " std::cout << \"h(double&) is called with a = \" << a << \"; a is doubled by the function.\" << std::endl;\n", " a *= 2.;\n", @@ -507,9 +613,9 @@ "outputs": [], "source": [ "{\n", - " h(5); // Ok\n", + " H(5); // Ok\n", " double x = 1.;\n", - " h(&x); // Ok\n", + " H(&x); // Ok\n", "}" ] }, @@ -528,7 +634,7 @@ "source": [ "{\n", " double x = 1.;\n", - " h(x); // COMPILATION ERROR: should it call h(double) or h(double& )?\n", + " H(x); // COMPILATION ERROR: should it call h(double) or h(double& )?\n", "}" ] }, @@ -547,7 +653,7 @@ "source": [ "{\n", " double x = 1.;\n", - " h(static_cast<double>(x)); // Ok\n", + " H(static_cast<double>(x)); // Ok\n", "}" ] }, @@ -567,7 +673,7 @@ "outputs": [], "source": [ "#include <iostream>\n", - "void h2(double a)\n", + "void H2(double a)\n", "{\n", " std::cout << \"h2(double) is called with a = \" << a << '.' << std::endl;\n", "}" @@ -580,7 +686,7 @@ "outputs": [], "source": [ "#include <iostream>\n", - "void h2(double* a) \n", + "void H2(double* a) \n", "{\n", " std::cout << \"h2(double*) is called with a = \" << *a << \"; a is doubled by the function.\" << std::endl;\n", " *a *= 2.;\n", @@ -597,10 +703,10 @@ " double x = 5.;\n", " double* ptr = &x;\n", " \n", - " h2(x); // call h2(double) \n", - " h2(ptr); // call h2(double*)\n", - " h2(*ptr); // call h2(double) \n", - " h2(&x); // call h2(double*)\n", + " H2(x); // call h2(double) \n", + " H2(ptr); // call h2(double*)\n", + " H2(*ptr); // call h2(double) \n", + " H2(&x); // call h2(double*)\n", "}" ] }, @@ -623,7 +729,7 @@ "source": [ "#include <iostream>\n", "\n", - "int min(int a, int b)\n", + "int Min(int a, int b)\n", "{\n", " std::cout << \"int version called!\" << std::endl;\n", " return a < b ? a : b;\n", @@ -638,7 +744,7 @@ "source": [ "#include <iostream>\n", "\n", - "double min(double a, double b)\n", + "double Min(double a, double b)\n", "{\n", " std::cout << \"double version called!\" << std::endl;\n", " return a < b ? a : b;\n", @@ -657,11 +763,11 @@ " float f1 { 3.14f }, f2 { -4.2f};\n", " short s1 { 5 }, s2 { 7 };\n", " \n", - " min(5, 7); // no ambiguity\n", - " min(i1, i2); // no ambiguity \n", - " min(f1, f2); // conversion to closest one\n", - " min(f1, d2); // conversion to closest one\n", - " min(s1, s2); // conversion to closest one \n", + " Min(5, 7); // no ambiguity\n", + " Min(i1, i2); // no ambiguity \n", + " Min(f1, f2); // conversion to closest one\n", + " Min(f1, d2); // conversion to closest one\n", + " Min(s1, s2); // conversion to closest one \n", "}" ] }, @@ -680,7 +786,7 @@ "source": [ "{\n", " unsigned int i1 { 5 }, i2 { 7 };\n", - " min(i1, i2); // COMPILATION ERROR: no 'obvious' best candidate!\n", + " Min(i1, i2); // COMPILATION ERROR: no 'obvious' best candidate!\n", "}" ] }, @@ -692,7 +798,7 @@ "source": [ "{\n", " long i1 { 5 }, i2 { 7 };\n", - " min(i1, i2); // COMPILATION ERROR: no 'obvious' best candidate!\n", + " Min(i1, i2); // COMPILATION ERROR: no 'obvious' best candidate!\n", "}" ] }, @@ -713,7 +819,7 @@ " float f1 { 5.f };\n", " int i1 { 5 };\n", " \n", - " min(f1, i1); // for i1 the 'int'version is better, but for f1 the 'double' is more appropriate...\n", + " Min(f1, i1); // for i1 the 'int'version is better, but for f1 the 'double' is more appropriate...\n", "}" ] }, @@ -933,12 +1039,12 @@ "\n", "{\n", " // Locally defined function.\n", - " auto square = [](double x) -> double\n", + " auto Square = [](double x) -> double\n", " {\n", " return x * x;\n", " };\n", " \n", - " std::cout << square(5.) << std::endl;\n", + " std::cout << Square(5.) << std::endl;\n", "}" ] }, @@ -964,18 +1070,18 @@ "\n", "{\n", " // Locally defined function.\n", - " auto square = [](double x) \n", + " auto Square = [](double x) \n", " {\n", " return x * x;\n", " };\n", " \n", - " auto cube = [](double x)\n", + " auto Cube = [](double x)\n", " {\n", " return x * x * x;\n", " };\n", "\n", " std::cout << \"Are the lambda prototypes the same type? \" \n", - " << (std::is_same<decltype(square), decltype(cube)>() ? \"true\" : \"false\") << std::endl;\n", + " << (std::is_same<decltype(Square), decltype(Cube)>() ? \"true\" : \"false\") << std::endl;\n", "}" ] }, @@ -999,12 +1105,12 @@ "{\n", " int a = 5;\n", " \n", - " auto a_plus_b = [](int b)\n", + " auto APlusB = [](int b)\n", " {\n", " return a + b;\n", " };\n", " \n", - " std::cout << a_plus_b(3) << std::endl; // COMPILATION ERROR: a is not known inside the lambda body.\n", + " std::cout << APlusB(3) << std::endl; // COMPILATION ERROR: a is not known inside the lambda body.\n", "}" ] }, @@ -1019,12 +1125,12 @@ "{\n", " int a = 5;\n", " \n", - " auto a_plus_b = [a](int b) // Notice the `[a]` here!\n", + " auto APlusB = [a](int b) // Notice the `[a]` here!\n", " {\n", " return a + b;\n", " };\n", " \n", - " std::cout << a_plus_b(3) << std::endl;\n", + " std::cout << APlusB(3) << std::endl;\n", "}" ] }, @@ -1046,12 +1152,12 @@ "{\n", " int a = 5;\n", " \n", - " auto add_to_a = [&a](int b) // Notice the `[&a]` here!\n", + " auto AddToA = [&a](int b) // Notice the `[&a]` here!\n", " {\n", " a += b;\n", " };\n", " \n", - " add_to_a(3);\n", + " AddToA(3);\n", " std::cout << a << std::endl; \n", "}" ] @@ -1192,7 +1298,7 @@ "metadata": {}, "outputs": [], "source": [ - "int multiply(int a, int b)\n", + "int Multiply(int a, int b)\n", "{\n", " return a * b;\n", "}" @@ -1204,7 +1310,7 @@ "metadata": {}, "outputs": [], "source": [ - "int add(int a, int b)\n", + "int Add(int a, int b)\n", "{\n", " return a + b;\n", "}" @@ -1216,8 +1322,8 @@ "metadata": {}, "outputs": [], "source": [ - "PrintFunctionCall(multiply, 5, 6);\n", - "PrintFunctionCall(add, 5, 6);" + "PrintFunctionCall(Multiply, 5, 6);\n", + "PrintFunctionCall(Add, 5, 6);" ] }, { @@ -1237,7 +1343,7 @@ "source": [ "## A very special function: __main__\n", "\n", - "Any C++ program must include one and only one `main` function. Its prototype is __int main(int argc, char** argv)__ where:\n", + "Any C++ program must include one and only one `main` function. Its prototype is `int main(int argc, char** argv)` where:\n", "* __argc__ is the number of arguments given on the command line. This is at least 1: the name of the program is one argument. For instance, if your program creates a _isPrime_ executable that takes an integer as argument, `argc` will return 2.\n", "* __argv__ is the list of arguments read on the command line, given as an array of C-strings. In our _isPrime_ example, __argv[0]__ is _isPrime_ and __argv[1]__ is the integer given.\n", "\n", @@ -1271,7 +1377,7 @@ "metadata": {}, "outputs": [], "source": [ - "inline double square(double x)\n", + "inline double Square(double x)\n", "{\n", " return x * x;\n", "}" @@ -1291,7 +1397,7 @@ "outputs": [], "source": [ "{\n", - " square(5.); // The compiler might substitute 5. * 5. to the actual function call here\n", + " Square(5.); // The compiler might substitute 5. * 5. to the actual function call here\n", "}" ] }, @@ -1306,7 +1412,7 @@ "* `inline` definitions must be provided in header file (see the [upcoming notebook](../6-InRealEnvironment/2-FileStructure.ipynb) that will deal extensively with the file structure to follow in a C++ program). You therefore pay the price in compilation time whenever you change its implementation (as we'll see more in detail in aforementioned notebook, modifying a header file yields more re-compilation).\n", "* Don't bother inlining functions with any complexity whatsoever, so if your function includes a loop or is more than few lines long, write a normal function instead. \n", "\n", - "The `square` example was sound: this is typically the kind of functions that might be inlined.\n", + "The `Square` example was sound: this is typically the kind of functions that might be inlined.\n", "\n", "Just to finish, my comparison with a macro was not fair; one of the known drawback of macros is perfectly handled: \n" ] @@ -1317,7 +1423,7 @@ "metadata": {}, "outputs": [], "source": [ - "#define SQUARE(x) ((x) * (x))" + "#define SQUARE(x) ((x) * (x)) // macro" ] }, { @@ -1330,7 +1436,7 @@ "\n", "{\n", " double x = 5.;\n", - " std::cout << square(++x) << std::endl; \n", + " std::cout << Square(++x) << std::endl; \n", "}\n", "\n", "{\n", diff --git a/1-ProceduralProgramming/6-Streams.ipynb b/1-ProceduralProgramming/6-Streams.ipynb index e640641de1f5695a5b6c29da259dfebc4e67779e..9ae1b5d04d814cb999326d62ec812871e363fab3 100644 --- a/1-ProceduralProgramming/6-Streams.ipynb +++ b/1-ProceduralProgramming/6-Streams.ipynb @@ -467,7 +467,7 @@ "source": [ "It is however useful to be aware of the pre-C++ 11 syntax, especially for the number to string conversion: 'arithmetic' operations between strings (as `+`) incur copies that are avoided with the `std::ostringstream` syntax... but the construction of such a `std::ostringstream` object is costly as well...\n", "\n", - "C++ 20 should provide a better looking and more efficient syntax with `std::format` (see [this page](https://en.cppreference.com/w/cpp/utility/format/format) for more details).\n" + "C++ 20 should provide a better looking and more efficient syntax with `std::format` (see [this page](https://en.cppreference.com/w/cpp/utility/format/format) for more details)... but unfortunately current support by compilers is [not great](https://en.cppreference.com/w/cpp/compiler_support).\n" ] }, { diff --git a/1-ProceduralProgramming/7-StaticAndConstexpr.ipynb b/1-ProceduralProgramming/7-StaticAndConstexpr.ipynb index 1476b14c4f6cbf91fd1b0c109e2c5ce7a557ccf9..2d7c8ea05188f8c76d1b753d49c7e35f4312d323 100644 --- a/1-ProceduralProgramming/7-StaticAndConstexpr.ipynb +++ b/1-ProceduralProgramming/7-StaticAndConstexpr.ipynb @@ -137,14 +137,14 @@ "source": [ "// Recursive function - Xeus cling may not appreciate if you call this cell several times.\n", "\n", - "auto fibonacci (std::size_t 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", + " return Fibonacci(n-1) + Fibonacci(n-2);\n", "}" ] }, @@ -155,8 +155,8 @@ "outputs": [], "source": [ "#include <iostream>\n", - "std::cout << fibonacci(5) << std::endl;\n", - "std::cout << fibonacci(10) << std::endl;" + "std::cout << Fibonacci(5) << std::endl;\n", + "std::cout << Fibonacci(10) << std::endl;" ] }, { @@ -165,7 +165,7 @@ "metadata": {}, "outputs": [], "source": [ - "double array[fibonacci(5)]; // COMPILATION ERROR!" + "double array[Fibonacci(5)]; // COMPILATION ERROR!" ] }, { @@ -188,14 +188,14 @@ "metadata": {}, "outputs": [], "source": [ - "constexpr auto fibonacci_const (std::size_t n) \n", + "constexpr auto FibonacciConstexpr (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", + " return FibonacciConstexpr(n-1) + FibonacciConstexpr(n-2);\n", "}" ] }, @@ -205,7 +205,7 @@ "metadata": {}, "outputs": [], "source": [ - "double array[fibonacci_const(5)]; // Ok!" + "double array[FibonacciConstexpr(5)]; // Ok!" ] }, { @@ -225,7 +225,7 @@ "\n", "int i = 7;\n", "++i; // i is by no stretch a compile time variable!\n", - "std::cout << fibonacci_const(7) << std::endl;" + "std::cout << FibonacciConstexpr(i) << std::endl;" ] }, { @@ -234,7 +234,7 @@ "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", + "- The function `FibonacciConstexpr` 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.\n", "\n", "We will see another use of `constexpr` in a [later notebook](../4-Templates/2-Specialization.ipynb#If-constexpr)." diff --git a/2-ObjectProgramming/1-Introduction.ipynb b/2-ObjectProgramming/1-Introduction.ipynb index 6131f5256e13fb8baa3cf894062667bf20d09a9f..61f13b800cffd1f7db856676c500994670d4ec6b 100644 --- a/2-ObjectProgramming/1-Introduction.ipynb +++ b/2-ObjectProgramming/1-Introduction.ipynb @@ -40,7 +40,7 @@ "#include <iostream>\n", "#include <cmath> // For std::sqrt\n", "\n", - "double norm(double v_x, double v_y, double v_z) \n", + "double Norm(double v_x, double v_y, double v_z) \n", "{ \n", " return std::sqrt( v_x * v_x + v_y * v_y + v_z * v_z ); \n", "}\n", @@ -51,14 +51,14 @@ " v1_y = 5.;\n", " v1_z = -2.;\n", "\n", - " std::cout << norm(v1_x, v1_y, v1_z) << std::endl;\n", + " std::cout << Norm(v1_x, v1_y, v1_z) << std::endl;\n", "\n", " double v2_x, v2_y, v2_z;\n", " v2_x = 2.;\n", " v2_y = 2.;\n", " v2_z = 4.;\n", "\n", - " std::cout << norm(v2_x, v2_y, v2_z) << std::endl;\n", + " std::cout << Norm(v2_x, v2_y, v2_z) << std::endl;\n", "}\n" ] }, @@ -66,7 +66,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The code above is completely oblivious of the close relationship between `x`, `y` and `z`, and for instance the norm function takes three distinct arguments. \n", + "The code above is completely oblivious of the close relationship between `x`, `y` and `z`, and for instance the `Norm` function takes three distinct arguments. \n", "\n", "This is not just an inconveniency: this can lead to mistake if there is an error in the variables passed:" ] @@ -87,7 +87,7 @@ " v2_y = 2.;\n", " v2_z = 4.;\n", "\n", - " const double norm1 = norm(v1_x, v1_y, v2_z); // probably not what was intended, but the program \n", + " const double norm1 = Norm(v1_x, v1_y, v2_z); // probably not what was intended, but the program \n", " // has no way to figure out something is fishy!\n", "}" ] @@ -123,7 +123,7 @@ "source": [ "#include <iostream>\n", "\n", - "double norm(Vector v)\n", + "double Norm(Vector v)\n", "{\n", " return std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z); \n", "}\n", @@ -134,14 +134,14 @@ " v1.y = 5.;\n", " v1.z = -2.;\n", "\n", - " std::cout << norm(v1) << std::endl;\n", + " std::cout << Norm(v1) << std::endl;\n", "\n", " Vector v2;\n", " v2.x = 2.;\n", " v2.y = 2.;\n", " v2.z = 4.;\n", "\n", - " std::cout << norm(v2) << std::endl; \n", + " std::cout << Norm(v2) << std::endl; \n", "}" ] }, @@ -149,7 +149,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Calling `norm` is now both more elegant (only one argument) and less dangerous (I can't mix by mistake coordinates from different objects).\n", + "Calling `Norm` is now both more elegant (only one argument) and less dangerous (I can't mix by mistake coordinates from different objects).\n", "\n", "Let's introduce at this point a bit of vocabulary:\n", "\n", @@ -174,9 +174,9 @@ "metadata": {}, "outputs": [], "source": [ - "// Xeus-cling issue (at least circa May 2021)\n", + "// Xeus-cling issue (at least circa September 2022)\n", "\n", - "struct Vector\n", + "struct VectorAndInstantiate\n", "{\n", " double x;\n", " double y;\n", @@ -219,7 +219,7 @@ "metadata": {}, "outputs": [], "source": [ - "double norm_without_copy(const Vector& v)\n", + "double NormWithoutCopy(const Vector& v)\n", "{\n", " return std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z); \n", "}\n", @@ -230,7 +230,7 @@ " v1.y = 5.;\n", " v1.z = -2.;\n", "\n", - " std::cout << norm_without_copy(v1) << std::endl;\n", + " std::cout << NormWithoutCopy(v1) << std::endl;\n", "}" ] }, @@ -249,7 +249,7 @@ "metadata": {}, "outputs": [], "source": [ - "double norm(const Vector* const v) // can keep the name here: no possible ambiguity\n", + "double Norm(const Vector* const v) // can keep the name here: no possible ambiguity\n", "{\n", " return std::sqrt((*v).x * (*v).x + (*v).y * (*v).y + (*v).z * (*v).z); \n", "}\n", @@ -260,7 +260,7 @@ " v1.y = 5.;\n", " v1.z = -2.;\n", "\n", - " std::cout << norm(&v1) << std::endl;\n", + " std::cout << Norm(&v1) << std::endl;\n", "}" ] }, @@ -277,7 +277,7 @@ "metadata": {}, "outputs": [], "source": [ - "double norm_with_pointer_shortcut(const Vector* const v)\n", + "double NormWithPointerShortcut(const Vector* const v)\n", "{\n", " return std::sqrt(v->x * v->x + v->y * v->y + v->z * v->z); \n", "}\n", @@ -288,7 +288,7 @@ " v1.y = 5.;\n", " v1.z = -2.;\n", "\n", - " std::cout << norm_with_pointer_shortcut(&v1) << std::endl;\n", + " std::cout << NormWithPointerShortcut(&v1) << std::endl;\n", "}" ] }, @@ -298,7 +298,7 @@ "source": [ "## Initialization of objects\n", "\n", - "So far, we have improved the way the `norm` function is called, but the initialization of a vector is still a bit tedious. Let's wrap up a function to ease that:" + "So far, we have improved the way the `Norm` function is called, but the initialization of a vector is still a bit tedious. Let's wrap up a function to ease that:" ] }, { @@ -307,7 +307,7 @@ "metadata": {}, "outputs": [], "source": [ - "void init(Vector& v, double x, double y, double z) \n", + "void Init(Vector& v, double x, double y, double z) \n", "{\n", " v.x = x;\n", " v.y = y;\n", @@ -323,8 +323,8 @@ "source": [ "{\n", " Vector v1;\n", - " init(v1, 1., 5., -2.);\n", - " std::cout << \"Norm = \" << norm(v1) << std::endl;\n", + " Init(v1, 1., 5., -2.);\n", + " std::cout << \"Norm = \" << Norm(v1) << std::endl;\n", "}" ] }, diff --git a/2-ObjectProgramming/2-Member-functions.ipynb b/2-ObjectProgramming/2-Member-functions.ipynb index d4b18875766d119ff41146afc5d545325504f83b..13ddecd55b7bb27aa097d91fad2c61307a488db4 100644 --- a/2-ObjectProgramming/2-Member-functions.ipynb +++ b/2-ObjectProgramming/2-Member-functions.ipynb @@ -25,7 +25,7 @@ "\n", "The struct we used previously would work the same in C code (with the exceptions of references: with a C compiler you would have to stick with pointers).\n", "\n", - "But when Bjarne Stroustrup created the C++, its main idea was to extend these structs into full-fledged **classes** (to the point the work name of his language was *C with classes*...)\n", + "But when Bjarne Stroustrup created the C++, its main idea was to extend these structs into full-fledged **classes** (to the extent that the working name of his language was *C with classes*...)\n", "\n", "One of the idea that was missing with original C `struct` was the possibility to add as well member functions:" ] @@ -44,14 +44,14 @@ " double y; \n", " double z;\n", " \n", - " void init(double x, double y, double z)\n", + " void Init(double x, double y, double z)\n", " {\n", " this->x = x;\n", " this->y = y;\n", " this->z = z;\n", " }\n", " \n", - " double norm()\n", + " double Norm()\n", " {\n", " return std::sqrt(x * x + y * y + z * z);\n", " }\n", @@ -69,8 +69,8 @@ "\n", "{\n", " Vector v;\n", - " v.init(5., 6., -4.2);\n", - " std::cout << v.norm() << std::endl;\n", + " v.Init(5., 6., -4.2);\n", + " std::cout << v.Norm() << std::endl;\n", "}" ] }, @@ -80,7 +80,7 @@ "source": [ "Let's do a bit of taxonomy here:\n", "\n", - "- `init()` and `norm()` are called **member functions** or **methods**. The same remark concerning C++ purist I did for member variables may be applied here.\n", + "- `Init()` and `Norm()` are called **member functions** or **methods**. The same remark concerning C++ purist I did for member variables may be applied here.\n", "- **Method** is used in other programming languages, but for some reason Julia creators used this exact term for an entirely different concept. So to put in a nutshell a C++ method is akin to a Python one but not to what Julia calls a method.\n", "- **Attributes** are in fact the data attributes AND the methods. It is however often used only to designate the data attributes.\n", "\n", @@ -93,9 +93,9 @@ "source": [ "## The `this` keyword\n", "\n", - "The `this->` may have puzzled you: it is a keyword to refer to the current object. So when you call `v.init(...)`, this is an implicit reference to `v`.\n", + "The `this->` may have puzzled you: it is a keyword to refer to the current object. So when you call `v.Init(...)`, this is an implicit reference to `v`.\n", "\n", - "In most cases, it might be altogether removed; we have to put it explicitly here solely because we named the `init` parameters with the same name as the data attribute. If not, we could have avoided to mention it completely.\n", + "In most cases, it might be altogether removed; we have to put it explicitly here solely because we named the `Init` parameters with the same name as the data attribute. If not, we could have avoided to mention it completely.\n", "\n", "An usual convention is to suffix data attributes with a `_` (**be careful**, attributes prefixed with a `_` is reserved by the C++ standard); doing so remove the need to the explicit `this`:" ] @@ -114,14 +114,14 @@ " double y_; \n", " double z_;\n", " \n", - " void init(double x, double y, double z)\n", + " void Init(double x, double y, double z)\n", " {\n", - " x_ = x;\n", + " x_ = x; // no need to `this`here as there is no ambiguity between data attribute name and parameter name\n", " y_ = y;\n", " z_ = z;\n", " }\n", " \n", - " double norm()\n", + " double Norm()\n", " {\n", " return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);\n", " }\n", @@ -139,8 +139,8 @@ "\n", "{\n", " Vector2 v;\n", - " v.init(5., 6., -4.2);\n", - " std::cout << v.norm() << std::endl;\n", + " v.Init(5., 6., -4.2);\n", + " std::cout << v.Norm() << std::endl;\n", "}" ] }, @@ -157,9 +157,9 @@ "source": [ "## Separating declaration and definition\n", "\n", - "We have defined above the method directly in the class declaration; which is not very clean. It is acceptable for a very short method as here, but for a more complex class and method it is better to separate explicitly both. In this case you will have:\n", + "We have defined so far the method directly in the class declaration; which is not very clean. It is acceptable for a very short method as here, but for a more complex class and method it is better to separate explicitly both. In this case you will have:\n", "\n", - "- On one side, usually in a header file:\n" + "- On one side, usually in a header file (we'll see the file structure in real C++ code [later on](../6-InRealEnvironment/2-FileStructure.ipynb)):\n" ] }, { @@ -174,9 +174,9 @@ " double y_; \n", " double z_;\n", " \n", - " void init(double x, double y, double z);\n", + " void Init(double x, double y, double z);\n", " \n", - " double norm();\n", + " double Norm();\n", "};" ] }, @@ -193,7 +193,7 @@ "metadata": {}, "outputs": [], "source": [ - "void Vector3::init(double x, double y, double z)\n", + "void Vector3::Init(double x, double y, double z)\n", "{\n", " x_ = x;\n", " y_ = y;\n", @@ -207,7 +207,7 @@ "metadata": {}, "outputs": [], "source": [ - "double Vector3::norm()\n", + "double Vector3::Norm()\n", "{\n", " return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);\n", "}" @@ -223,8 +223,8 @@ "\n", "{\n", " Vector3 v;\n", - " v.init(5., 6., -4.2);\n", - " std::cout << v.norm() << std::endl;\n", + " v.Init(5., 6., -4.2);\n", + " std::cout << v.Norm() << std::endl;\n", "}" ] }, @@ -254,9 +254,9 @@ "source": [ "#include <iostream>\n", "\n", - "void print_norm(const Vector3& v)\n", + "void PrintNorm(const Vector3& v)\n", "{\n", - " std::cout << v.norm() << std::endl;\n", + " std::cout << v.Norm() << std::endl; // COMPILATION ERROR\n", "}" ] }, @@ -266,7 +266,7 @@ "source": [ "... we see that doesn't compile. So what is happening?\n", "\n", - "The issue here is that the function `print_norm` takes as argument a constant reference, and has to guarantee the underlying object is not modified in the process. A \"patch\" would be to define it without the const:\n", + "The issue here is that the function `PrintNorm` takes as argument a constant reference, and has to guarantee the underlying object is not modified in the process. A \"patch\" would be to define it without the const:\n", "\n" ] }, @@ -278,9 +278,9 @@ "source": [ "#include <iostream>\n", "\n", - "void print_norm_no_const(Vector3& v) // BAD IDEA!\n", + "void PrintNormNoConst(Vector3& v) // BAD IDEA!\n", "{\n", - " std::cout << v.norm() << std::endl;\n", + " std::cout << v.Norm() << std::endl;\n", "}" ] }, @@ -292,8 +292,8 @@ "source": [ "{\n", " Vector3 v;\n", - " v.init(5., 6., -4.2);\n", - " print_norm_no_const(v);\n", + " v.Init(5., 6., -4.2);\n", + " PrintNormNoConst(v);\n", "}" ] }, @@ -303,9 +303,9 @@ "source": [ "Why is it such a poor idea? C++ is a compiled language, and this has its (many) pros and (many) cons. One of the advantages is to be able to leverage the compilation to detect at early time something is amiss. Here the compilation error is a good way to see we might be doing something wrong.\n", "\n", - "The sketchy \"patch\" I provided would be akin to ignoring the `const` feature almost entirely when objects are concerned.\n", + "The sketchy \"patch\" I provided would be akin to ignoring the `const` feature almost entirely whenever objects are concerned.\n", "\n", - "The proper way is in fact quite the opposite: we may specify when writing a method that it is not allowed to modify the state of the object:" + "The proper way to solve the issue is in fact quite the opposite: we may specify when writing a method that it is not allowed to modify the state of the object:" ] }, { @@ -320,11 +320,11 @@ " double y_; \n", " double z_;\n", " \n", - " void init(double x, double y, double z);\n", + " void Init(double x, double y, double z);\n", " \n", - " double norm() const; // notice the additional keyword!\n", + " double Norm() const; // notice the additional keyword!\n", " \n", - " void dont_put_const_everywhere() const;\n", + " void DontPutConstEverywhere() const;\n", "};" ] }, @@ -334,7 +334,7 @@ "metadata": {}, "outputs": [], "source": [ - "void Vector4::init(double x, double y, double z)\n", + "void Vector4::Init(double x, double y, double z)\n", "{\n", " x_ = x;\n", " y_ = y;\n", @@ -348,7 +348,7 @@ "metadata": {}, "outputs": [], "source": [ - "double Vector4::norm() const\n", + "double Vector4::Norm() const\n", "{\n", " return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);\n", "}" @@ -369,15 +369,15 @@ "source": [ "#include <iostream>\n", "\n", - "void print_norm(const Vector4& v)\n", + "void PrintNorm(const Vector4& v)\n", "{\n", - " std::cout << v.norm() << std::endl;\n", + " std::cout << v.Norm() << std::endl;\n", "}\n", "\n", "{\n", " Vector4 v;\n", - " v.init(5., 6., -4.2);\n", - " print_norm(v);\n", + " v.Init(5., 6., -4.2);\n", + " PrintNorm(v);\n", "}" ] }, @@ -394,7 +394,7 @@ "metadata": {}, "outputs": [], "source": [ - "void Vector4::dont_put_const_everywhere() const\n", + "void Vector4::DontPutConstEverywhere() const\n", "{\n", " x_ = 0.; // ERROR!\n", "}" @@ -422,9 +422,9 @@ " double z_;\n", " mutable unsigned int Nnorm_calls_;\n", " \n", - " void init(double x, double y, double z);\n", + " void Init(double x, double y, double z);\n", " \n", - " double norm() const; \n", + " double Norm() const; \n", "};" ] }, @@ -434,7 +434,7 @@ "metadata": {}, "outputs": [], "source": [ - "void Vector5::init(double x, double y, double z)\n", + "void Vector5::Init(double x, double y, double z)\n", "{\n", " x_ = x;\n", " y_ = y;\n", @@ -449,7 +449,7 @@ "metadata": {}, "outputs": [], "source": [ - "double Vector5::norm() const\n", + "double Vector5::Norm() const\n", "{\n", " ++Nnorm_calls_;\n", " return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);\n", @@ -462,12 +462,14 @@ "metadata": {}, "outputs": [], "source": [ + "#include <iostream>\n", + "\n", "{\n", " Vector5 v;\n", - " v.init(5., 6., -4.2);\n", + " v.Init(5., 6., -4.2);\n", " for (int i = 0; i < 5; ++i)\n", - " v.norm();\n", - " std::cout << \"Method 'norm()' was called \" << v.Nnorm_calls_ << \" times.\" << std::endl; \n", + " v.Norm();\n", + " std::cout << \"Method 'Norm()' was called \" << v.Nnorm_calls_ << \" times.\" << std::endl; \n", "}" ] }, diff --git a/2-ObjectProgramming/3-constructors-destructor.ipynb b/2-ObjectProgramming/3-constructors-destructor.ipynb index ced0b1d2f74da0a198825d98467ce46379acfe72..c2a257ef8c72bceac872977d1e7956d9591d1219 100644 --- a/2-ObjectProgramming/3-constructors-destructor.ipynb +++ b/2-ObjectProgramming/3-constructors-destructor.ipynb @@ -23,7 +23,7 @@ "source": [ "## Introduction to base constructor\n", "\n", - "In fact, our previous `init()` function is meant to be realized through a dedicated method called a **constructor**. By convention, a constructor shares the name of the struct or class.\n", + "In fact, our previous `Init()` function is meant to be realized through a dedicated method called a **constructor**. By convention, a constructor shares the name of the struct or class.\n", "\n", "Several constructors may be defined for a given class, provided there is no signature overlap." ] @@ -44,7 +44,7 @@ " \n", " Vector(double x, double y, double z); // Another constructor\n", " \n", - " double norm() const;\n", + " double Norm() const;\n", "};" ] }, @@ -88,7 +88,7 @@ "metadata": {}, "outputs": [], "source": [ - "double Vector::norm() const\n", + "double Vector::Norm() const\n", "{\n", " return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_);\n", "}" @@ -104,7 +104,7 @@ "\n", "{\n", " Vector v(5., 6., -4.2); // note the creation of an object with a constructor call.\n", - " std::cout << v.norm() << std::endl;\n", + " std::cout << v.Norm() << std::endl;\n", "}" ] }, @@ -127,7 +127,7 @@ "\n", "{\n", " Vector v; // no parenthesis here!\n", - " std::cout << v.norm() << std::endl;\n", + " std::cout << v.Norm() << std::endl;\n", "}" ] }, @@ -150,7 +150,7 @@ "\n", "{\n", " auto v = Vector(5, 10, 15); // auto-to-stick syntax: a new perfectly fine way to declare an object.\n", - " std::cout << v.norm() << std::endl;\n", + " std::cout << v.Norm() << std::endl;\n", "}" ] }, @@ -169,7 +169,7 @@ "source": [ "{\n", " auto v = Vector(); // auto-to-stick syntax\n", - " std::cout << v.norm() << std::endl; \n", + " std::cout << v.Norm() << std::endl; \n", "}" ] }, @@ -182,39 +182,13 @@ "I advise you to read this very interesting [FluentCpp post](https://www.fluentcpp.com/2018/09/28/auto-stick-changing-style/) about this syntax which ponders about the relunctance we might have to embrace evolution of our languages - so the reading is of interest even for developers using other languages." ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### \"braces\" (`{}`) for constructor calls\n", - "\n", - "Another alternate syntax that removes the ambiguity is the use of braces (`{}`):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#include <iostream>\n", - "\n", - "{\n", - " Vector v1{}; // Constructor Vector()\n", - " std::cout << v1.norm() << std::endl;\n", - "\n", - " Vector v2{5., 6., -4.2}; // The other constructor Vector(double x, double y, double z)\n", - " std::cout << v2.norm() << std::endl;\n", - "}" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Initializing a reference data attribute\n", "\n", - "We saw above a new syntax to initialize data attributes, but there is no harm defining the same as we did in our former `init()` method. That is however not true when there is a reference data attribute, for which the reference must absolutely be defined with the new syntax:" + "We saw above a new syntax to initialize data attributes, but there is no harm defining the same as we did in our former `Init()` method. That is however not true when there is a reference data attribute, for which the reference must absolutely be defined with the new syntax:" ] }, { @@ -447,7 +421,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This must seem messy at first sight, but doing otherwise would be nightmarish: if you define a very complex class that must be carefully initialized with well thought-out arguments, you do not want your end-user to bypass this with a inconsiderate call to a constructor without arguments!\n", + "This must seem messy at first sight, but doing otherwise would be nightmarish: if you define a very complex class that must be carefully initialized with well thought-out arguments, you do not want your end-user to bypass this with an inconsiderate call to a constructor without arguments!\n", "\n", "If you want to enable back constructor without arguments, you may do so by defining it explicitly. C++11 introduced a nice way to do so (provided you wish an empty body - if not define it explicitly yourself):" ] diff --git a/2-ObjectProgramming/4-encapsulation.ipynb b/2-ObjectProgramming/4-encapsulation.ipynb index 217c6386351f100d3b3751462f65ad301fbe0d5b..983115c5f8bff8699e41cdf66750c27d5234f05a 100644 --- a/2-ObjectProgramming/4-encapsulation.ipynb +++ b/2-ObjectProgramming/4-encapsulation.ipynb @@ -727,7 +727,7 @@ " \n", " Vector(double x, double y, double z);\n", " \n", - " friend double norm(const Vector&);\n", + " friend double Norm(const Vector&);\n", " \n", " friend class PrintVector; // In C++11 `class` became optional here.\n", " \n", @@ -760,7 +760,7 @@ "source": [ "#include <cmath>\n", "\n", - "double norm(const Vector& v)\n", + "double Norm(const Vector& v)\n", "{\n", " return std::sqrt(v.x_ * v.x_ + v.y_ * v.y_ + v.z_ * v.z_); // OK!\n", "}" @@ -831,7 +831,7 @@ "Obviously, friendship should be used with parsimony... But it's not that much of a deal-breaker as it may seem:\n", "\n", "* The friendship must be defined in the class declaration. It means you can't use it to bypass a class encapsulation without modifying this code directly.\n", - "* The friendship is granted to a very specific function or class, and this class must be known when the class is defined. So an ill-intentioned user can't use the function prototype to sneak into your class private parts (in fact [forward declaration](../6-InRealEnvironment/2-FileStructure.ipynb#Forward-declaration) is an exception to current statement)." + "* The friendship is granted to a very specific function or class, and this class must be known when the class is defined. So an ill-intentioned user can't use the function prototype to sneak into your class private parts (in fact to be completely honest we will see an exception to this statement later with [forward declaration](../6-InRealEnvironment/2-FileStructure.ipynb#Forward-declaration))." ] }, { diff --git a/2-ObjectProgramming/5-static.ipynb b/2-ObjectProgramming/5-static.ipynb index d11c49f9b020004cf1ecc0cbf80f40eb2e43de61..5d1c41649b95935bcdea5403c019191ee322fbca 100644 --- a/2-ObjectProgramming/5-static.ipynb +++ b/2-ObjectProgramming/5-static.ipynb @@ -39,7 +39,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Sometimes, a data is related to the _class_ itself rather than to the object. The way to indicate this is to put a **static** keyword in front of the attribute that is not especially related to the instantiated object but rather common to all instances.\n", + "Sometimes, a data is related to the _class_ itself rather than to the object. The way to indicate this is to put a `static` keyword in front of the attribute that is not especially related to the instantiated object but rather common to all instances.\n", "\n", "Static attributes are following the exact same rules as the standard ones regarding the access status (public or private)." ] @@ -118,7 +118,7 @@ " \n", " ~Class();\n", " \n", - " static int Ninstance_;\n", + " static unsigned int Ninstance_;\n", "};\n", "\n", "Class::Class()\n", @@ -132,7 +132,7 @@ "}\n", "\n", "// IMPORTANT: this line must be put in a compiled file!\n", - "int Class::Ninstance_ = 0;\n", + "unsigned int Class::Ninstance_ = 0;\n", "\n", "void Print()\n", "{\n", @@ -183,7 +183,7 @@ " \n", " ~Class3();\n", " \n", - " static int& Ninstance(); // notice the reference and the fact it's now a method\n", + " static unsigned int& Ninstance(); // notice the reference and the fact it's now a method\n", "};" ] }, @@ -193,9 +193,9 @@ "metadata": {}, "outputs": [], "source": [ - "int& Class3::Ninstance()\n", + "unsigned int& Class3::Ninstance()\n", "{\n", - " static int ret = 0; // the initial value, please notice the use of C static here!\n", + " static unsigned int ret = 0; // the initial value, please notice the use of C static here!\n", " return ret;\n", "}" ] @@ -276,7 +276,7 @@ "source": [ "### New C++ 17 fix\n", "\n", - "C++ 17 actually provides a way to define the value in the header file with the `inline` keyword:" + "C++ 17 actually provides a way to define the value in the header file within the class declaration with the `inline` keyword:" ] }, { @@ -291,7 +291,7 @@ " \n", " ~Class4();\n", " \n", - " static inline int Ninstance_ = 0;\n", + " static inline unsigned int Ninstance_ = 0;\n", "};" ] }, diff --git a/2-ObjectProgramming/6-inheritance.ipynb b/2-ObjectProgramming/6-inheritance.ipynb index 982604c0378ed3e3babef1b932820ff79fb2da7e..ff46ffd10f6ffac07ea38c6e054c9586dab67bae 100644 --- a/2-ObjectProgramming/6-inheritance.ipynb +++ b/2-ObjectProgramming/6-inheritance.ipynb @@ -128,7 +128,7 @@ " \n", "The base class part is constructed first, and then the elements specific to the derived class are added (that will be important - we will come back to that).\n", " \n", - "Likewise, destruction is performed in reverse order: first the specific part of the derived class, then the base class (in most cases you don't have to care about that).\n", + "Likewise, destruction is performed in reverse order: first the specific parts of the derived class, then the base class (in most cases you don't have to care about that).\n", "\n", "### Multiple layer of inheritance\n", "\n", @@ -229,7 +229,7 @@ "source": [ "BlueVehicle::BlueVehicle(motor_type type)\n", ": Vehicle(type), // mandatory call of the non-default constructor \n", - "BlueObjects() // not mandatory: default constructor called\n", + "BlueObjects() // not mandatory: default constructor called anyway if not specified explicitly\n", "{ }" ] }, @@ -755,7 +755,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "You may have noticed the data attribute is stored as a pointer: this avoids issues related to the initialization of the object when it uses a non-default constructor. This doesn't mean using directly an object is impossible, just that extra conditions must be fulfilled in this case.\n" + "You may have noticed the data attribute is stored as a pointer: this avoids issues related to the initialization of the object when it uses a non-default constructor (here as `Rectangle` doesn't get a default constructor your compiler would yell if the data attribute was directly a `Rectangle` rather than a pointer). \n", + "\n", + "This doesn't mean using directly an object is impossible, just that extra conditions must be fulfilled in this case.\n" ] }, { diff --git a/2-ObjectProgramming/7-polymorphism.ipynb b/2-ObjectProgramming/7-polymorphism.ipynb index 0eacc5ce9cf78e1a1a6994e0f9c7d23b3f23b2d6..5d9e885d40c51e5106fec082814ee87071d11fa7 100644 --- a/2-ObjectProgramming/7-polymorphism.ipynb +++ b/2-ObjectProgramming/7-polymorphism.ipynb @@ -410,7 +410,7 @@ " PolymorphicButClumsyVehicle* list[3] = { v, b, c };\n", " \n", " for (auto i = 0ul; i < 3ul; ++i)\n", - " list[i]->Print(); // No compilation error, but clearly not what we intended...\n", + " list[i]->Print();\n", " \n", " delete v;\n", " delete b;\n", @@ -535,6 +535,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "_(don't worry if you get a warning - if so the compiler does its job well and we'll see shortly why)_\n", + "\n", "**Beware:** You **must** provide a definition for all non pure-virtual methods in your class. Not doing so leads to a somewhat cryptic error at link-time.\n", "\n", "You are not required to provide a definition for a pure virtual method, and you won't most of the time... But you might provide one if you want to do so, for instance to provide an optional default instantiation for the method in derived classes:" @@ -912,7 +914,7 @@ "source": [ "You may in fact explicitly tells the `c` pointer needs to be interpreted as a `PolymorphicThermicCar`:\n", "\n", - "**Xeus-cling issue:** Here cling may not be able to run it (depends on the version used) but it is accepted rightfully by a full-fledged compiler (see for instance [@Coliru](http://coliru.stacked-crooked.com/a/22fff15d28c93a17)):" + "**Xeus-cling issue:** Here cling may not be able to run it (depends on the version used - it didn't in 2021 but seems to work in 2022) but it is accepted rightfully by a full-fledged compiler (see for instance [@Coliru](http://coliru.stacked-crooked.com/a/22fff15d28c93a17)):" ] }, { @@ -1155,8 +1157,8 @@ "\n", "So to put in a nutshell, 99 % of the time:\n", "\n", - "* If a class of yours is intended to be inherited from, make its destructor virtual.\n", - "* If not, mark it as final.\n" + "* If a class of yours is intended to be inherited from, make its destructor `virtual`.\n", + "* If not, mark the class as `final`.\n" ] }, { @@ -1167,7 +1169,7 @@ "\n", "A very important point: I lost time years ago with this because I didn't read carefully enough item 9 of \\cite{Meyers2005}...\n", "\n", - "Due to the way construction occurs, never call a virtual method in a constructor: it won't perform the dynamic binding as you would like it to.\n", + "Due to the way construction occurs, never call a virtual method in a constructor: it won't perform the dynamic binding as you would like it to (and your compiler won't help you here).\n", "\n" ] }, diff --git a/3-Operators/1-Intro.ipynb b/3-Operators/1-Intro.ipynb index 19f8c55761a7386822fb4972ef86d07c551b1320..93dda12deab24a7a610b9a2b57e2d8f9a2b32373 100644 --- a/3-Operators/1-Intro.ipynb +++ b/3-Operators/1-Intro.ipynb @@ -144,7 +144,9 @@ "\n", "## Overloading an operator\n", "\n", - "To overload an operator, the syntax is just the keyword **operator** followed by the operator to overload." + "To overload an operator, the syntax is just the keyword **operator** followed by the operator to overload. In the following we will just replace the `Add` method by `operator+`.\n", + "\n", + "The following code illustrate how to do so... but unfortunately doesn't run with Xeus CLing (you may play with it [@Coliru](https://coliru.stacked-crooked.com/a/765b9dc1e2b73c71)).\n" ] }, { @@ -153,33 +155,75 @@ "metadata": {}, "outputs": [], "source": [ - "class Vector2\n", + "// DOESN'T RUN WITH XEUS-CLING\n", + "\n", + "#include <iostream>\n", + "\n", + "class VectorPlus\n", "{\n", " public :\n", "\n", - " Vector2(double x, double y, double z);\n", + " VectorPlus(double x, double y, double z);\n", " \n", - " Vector2() = default;\n", + " VectorPlus() = default;\n", " \n", " void Print() const;\n", " \n", - " // I would rather put the definition outside but Xeus-cling doesn't seem to accept this.\n", - " Vector2 operator+(const Vector2& v) const\n", - " {\n", - " Vector2 ret;\n", - " ret.x_ = x_ + v.x_;\n", - " ret.y_ = y_ + v.y_;\n", - " ret.z_ = z_ + v.z_;\n", - " \n", - " return ret;\n", - " }\n", + " // Friendship as free function operator+ wouldn't otherwise be allowed to access data attributes.\n", + " friend VectorPlus operator+(const VectorPlus& v1, const VectorPlus& v2);\n", "\n", " private :\n", " \n", " double x_ = 0.;\n", " double y_ = 0.;\n", " double z_ = 0.;\n", - "}; " + "}; \n", + "\n", + "VectorPlus::VectorPlus(double x, double y, double z)\n", + ": x_(x),\n", + "y_(y),\n", + "z_(z)\n", + "{ }\n", + "\n", + "\n", + "void VectorPlus::Print() const\n", + "{\n", + " std::cout << \"(\" << x_ << \", \" << y_ << \", \" << z_ << \")\" << std::endl;\n", + "}\n", + "\n", + "\n", + "VectorPlus operator+(const VectorPlus& v1, const VectorPlus& v2) \n", + "{ \n", + " // Provides a symmetric implementation of operator +: both vectors are at the same level! \n", + " VectorPlus ret;\n", + " ret.x_ = v1.x_ + v2.x_;\n", + " ret.y_ = v1.y_ + v2.y_;\n", + " ret.z_ = v1.z_ + v2.z_;\n", + " \n", + " return ret;\n", + "}\n", + "\n", + "\n", + "int main(int argc, char** argv)\n", + "{\n", + " VectorPlus v1(3., 5., 7.);\n", + " VectorPlus v2(7., 5., 3.);\n", + " \n", + " VectorPlus v3 = v1 + v2; // Nicer syntax!\n", + " v3.Print();\n", + " \n", + " VectorPlus v4 = operator+(v1, v2); // but \"usual\" method syntax is possible as well\n", + " v4.Print();\n", + " \n", + " return EXIT_SUCCESS;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It should be noted that for most operators it is also possible to define them as a class method instead:" ] }, { @@ -188,18 +232,33 @@ "metadata": {}, "outputs": [], "source": [ - "// How the definition outside the class would look like: nothing new or surprising here...\n", - "// Not accepted by Xeus-cling ().\n", - "\n", - "Vector2 Vector2::operator+(const Vector2& v) const\n", + "class VectorPlusAsMethod\n", "{\n", - " Vector2 ret;\n", - " ret.x_ = x_ + v.x_;\n", - " ret.y_ = y_ + v.y_;\n", - " ret.z_ = z_ + v.z_;\n", + " public :\n", "\n", - " return ret;\n", - "}\n" + " VectorPlusAsMethod(double x, double y, double z);\n", + " \n", + " VectorPlusAsMethod() = default;\n", + " \n", + " void Print() const;\n", + " \n", + " // I would rather put the definition outside but Xeus-cling doesn't seem to accept this.\n", + " VectorPlusAsMethod operator+(const VectorPlusAsMethod& v) const\n", + " {\n", + " VectorPlusAsMethod ret;\n", + " ret.x_ = x_ + v.x_;\n", + " ret.y_ = y_ + v.y_;\n", + " ret.z_ = z_ + v.z_;\n", + " \n", + " return ret;\n", + " }\n", + "\n", + " private :\n", + " \n", + " double x_ = 0.;\n", + " double y_ = 0.;\n", + " double z_ = 0.;\n", + "}; " ] }, { @@ -208,7 +267,7 @@ "metadata": {}, "outputs": [], "source": [ - "Vector2::Vector2(double x, double y, double z)\n", + "VectorPlusAsMethod::VectorPlusAsMethod(double x, double y, double z)\n", ": x_(x),\n", "y_(y),\n", "z_(z)\n", @@ -223,7 +282,7 @@ "source": [ "#include <iostream>\n", "\n", - "void Vector2::Print() const\n", + "void VectorPlusAsMethod::Print() const\n", "{\n", " std::cout << \"(\" << x_ << \", \" << y_ << \", \" << z_ << \")\" << std::endl;\n", "}" @@ -236,11 +295,14 @@ "outputs": [], "source": [ "{\n", - " Vector2 v1(3., 5., 7.);\n", - " Vector2 v2(7., 5., 3.);\n", + " VectorPlusAsMethod v1(3., 5., 7.);\n", + " VectorPlusAsMethod v2(7., 5., 3.);\n", " \n", - " Vector2 v3 = v1 + v2;\n", + " VectorPlusAsMethod v3 = v1 + v2;\n", " v3.Print();\n", + " \n", + " VectorPlusAsMethod v4 = v1.operator+(v2); // but \"usual\" method syntax is possible as well\n", + " v4.Print();\n", "}" ] }, @@ -248,80 +310,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We see in the definition of the `operator+` that both `Vector2` added aren't symmetric: one is the data attribute while the other is the data attribute of an object given as an argument.\n", - "\n", - "As a side note, please remark the `operator+` implementation is able to reach the private data attributes of the argument `v`; this means the private status is set **at class level** and not at object level.\n", - "\n", - "\n", - "It is actually possible to define the operator as a free function, thus providing a more symmetric implementation:\n", - "\n", - "**Xeus-cling issue**: cling doesn't accept operator definition outside of class; please use [@Coliru](https://coliru.stacked-crooked.com/a/626efa4fb6a02915):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "// Xeus-cling issue: doesn't compile!\n", - "\n", - "#include <iostream>\n", - "\n", - "class Vector3\n", - "{\n", - " public :\n", - "\n", - " Vector3(double x, double y, double z);\n", - " \n", - " Vector3() = default;\n", - " \n", - " void Print() const;\n", - " \n", - " friend Vector3 operator+(const Vector3& v1, const Vector3& v2);\n", - "\n", - " private :\n", - " \n", - " double x_ = 0.;\n", - " double y_ = 0.;\n", - " double z_ = 0.;\n", - "}; \n", - "\n", - "Vector3::Vector3(double x, double y, double z)\n", - ": x_(x),\n", - "y_(y),\n", - "z_(z)\n", - "{ }\n", + "We see here in the definition of the `operator+` that both `VectorPlusAsMethod` objects added aren't symmetric: one is the data attribute while the other is the data attribute of an object given as an argument. If is often advised to rather use the free function version to avoid this asymmetry, but it is mostly a matter of taste as both are working.\n", "\n", - "\n", - "void Vector3::Print() const\n", - "{\n", - " std::cout << \"(\" << x_ << \", \" << y_ << \", \" << z_ << \")\" << std::endl;\n", - "}\n", - "\n", - "\n", - "Vector3 operator+(const Vector3& v1, const Vector3& v2) \n", - "{ \n", - " // Provides a symmetric implementation of operator +: both vectors are at the same level! \n", - " Vector3 ret;\n", - " ret.x_ = v1.x_ + v2.x_;\n", - " ret.y_ = v1.y_ + v2.y_;\n", - " ret.z_ = v1.z_ + v2.z_;\n", - " \n", - " return ret;\n", - "}\n", - "\n", - "\n", - "int main(int argc, char** argv)\n", - "{\n", - " Vector3 v1(3., 5., 7.);\n", - " Vector3 v2(7., 5., 3.);\n", - " \n", - " Vector3 v3 = v1 + v2;\n", - " v3.Print();\n", - " \n", - " return EXIT_SUCCESS;\n", - "}" + "As a side note, please remark the `VectorPlusAsMethod::operator+` implementation is able to reach the private data attributes of the argument `v`; this means the private status is set **at class level** and not at object level." ] }, { @@ -340,20 +331,20 @@ "metadata": {}, "outputs": [], "source": [ - "class Vector4\n", + "class VectorPlusDouble\n", "{\n", " public :\n", "\n", - " Vector4(double x, double y, double z);\n", + " VectorPlusDouble(double x, double y, double z);\n", " \n", - " Vector4() = default;\n", + " VectorPlusDouble() = default;\n", " \n", " void Print() const;\n", " \n", " // Defined in the class declaration due to Xeus-cling limitation.\n", - " Vector4 operator+(double value) const\n", + " VectorPlusDouble operator+(double value) const\n", " {\n", - " Vector4 ret;\n", + " VectorPlusDouble ret;\n", " ret.x_ = x_ + value;\n", " ret.y_ = y_ + value;\n", " ret.z_ = z_ + value;\n", @@ -377,7 +368,7 @@ "source": [ "#include <iostream>\n", "\n", - "void Vector4::Print() const\n", + "void VectorPlusDouble::Print() const\n", "{\n", " std::cout << \"(\" << x_ << \", \" << y_ << \", \" << z_ << \")\" << std::endl;\n", "}" @@ -389,7 +380,7 @@ "metadata": {}, "outputs": [], "source": [ - "Vector4::Vector4(double x, double y, double z)\n", + "VectorPlusDouble::VectorPlusDouble(double x, double y, double z)\n", ": x_(x),\n", "y_(y),\n", "z_(z)\n", @@ -403,8 +394,8 @@ "outputs": [], "source": [ "{\n", - " Vector4 vector(5., 3.2, -1.);\n", - " Vector4 vector_plus_5 = vector + 5.;\n", + " VectorPlusDouble vector(5., 3.2, -1.);\n", + " VectorPlusDouble vector_plus_5 = vector + 5.;\n", " vector_plus_5.Print();\n", "}" ] @@ -423,8 +414,8 @@ "outputs": [], "source": [ "{\n", - " Vector4 vector(5., 3.2, -1.);\n", - " Vector4 vector_plus_5 = vector.operator+(5.);\n", + " VectorPlusDouble vector(5., 3.2, -1.);\n", + " VectorPlusDouble vector_plus_5 = vector.operator+(5.);\n", "}" ] }, @@ -442,8 +433,8 @@ "outputs": [], "source": [ "{\n", - " Vector4 vector(5., 3.2, -1.);\n", - " Vector4 vector_plus_5 = 5. + vector; // COMPILATION ERROR!\n", + " VectorPlusDouble vector(5., 3.2, -1.);\n", + " VectorPlusDouble vector_plus_5 = 5. + vector; // COMPILATION ERROR!\n", "}" ] }, @@ -451,7 +442,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "If you want it to be possible, you have to define the operator with arguments in both orders; you therefore need to use out-of-class prototype of the function (can't show it currently due to Xeus-cling limitation, available [@Coliru](https://coliru.stacked-crooked.com/a/bb8130aec13bdf26)).\n", + "If you want it to be possible, you have to define the operator with arguments in both orders; you therefore need to use out-of-class prototype of the function (can't show it currently due to Xeus-cling limitation, available [@Coliru](https://coliru.stacked-crooked.com/a/c03fc40e0f0a8ea0)).\n", "\n", "Of course, it is a **good practice** to define one in way of the other:" ] @@ -467,19 +458,19 @@ "#include <cstdlib>\n", "#include <iostream>\n", "\n", - "class Vector5\n", + "class VectorPlusDoubleCommutative\n", "{\n", " public :\n", "\n", - " Vector5(double x, double y, double z);\n", + " VectorPlusDoubleCommutative(double x, double y, double z);\n", " \n", - " Vector5() = default;\n", + " VectorPlusDoubleCommutative() = default;\n", " \n", " void Print() const;\n", " \n", - " friend Vector5 operator+(const Vector5& v, double value);\n", + " friend VectorPlusDoubleCommutative operator+(const VectorPlusDoubleCommutative& v, double value);\n", " \n", - " friend Vector5 operator+(double value, const Vector5& v);\n", + " friend VectorPlusDoubleCommutative operator+(double value, const VectorPlusDoubleCommutative& v);\n", "\n", " private :\n", " \n", @@ -490,13 +481,13 @@ "\n", "\n", "\n", - "void Vector5::Print() const\n", + "void VectorPlusDoubleCommutative::Print() const\n", "{\n", " std::cout << \"(\" << x_ << \", \" << y_ << \", \" << z_ << \")\" << std::endl;\n", "}\n", "\n", "\n", - "Vector5::Vector5(double x, double y, double z)\n", + "VectorPlusDoubleCommutative::VectorPlusDoubleCommutative(double x, double y, double z)\n", ": x_(x),\n", "y_(y),\n", "z_(z)\n", @@ -504,9 +495,9 @@ "\n", "\n", "\n", - "Vector5 operator+(const Vector5& v, double value)\n", + "VectorPlusDoubleCommutative operator+(const VectorPlusDoubleCommutative& v, double value)\n", "{\n", - " Vector5 ret;\n", + " VectorPlusDoubleCommutative ret;\n", " \n", " ret.x_ = v.x_ + value;\n", " ret.y_ = v.y_ + value;\n", @@ -516,17 +507,17 @@ "}\n", "\n", "\n", - "Vector5 operator+(double value, const Vector5& v)\n", + "VectorPlusDoubleCommutative operator+(double value, const VectorPlusDoubleCommutative& v)\n", "{\n", - " return v + value;\n", + " return v + value; // good practice: make it rely upon the other `operator+` defined!\n", "}\n", "\n", "\n", "int main(int argc, char** argv)\n", "{\n", - " Vector5 vector(5., 3.2, -1.);\n", - " Vector5 vector_plus_5 = vector + 5.;\n", - " Vector5 vector_plus_5_commutated = 5. + vector;\n", + " VectorPlusDoubleCommutative vector(5., 3.2, -1.);\n", + " VectorPlusDoubleCommutative vector_plus_5 = vector + 5.;\n", + " VectorPlusDoubleCommutative vector_plus_5_commutated = 5. + vector;\n", " \n", " vector_plus_5.Print();\n", " vector_plus_5_commutated.Print();\n", @@ -669,6 +660,8 @@ "metadata": {}, "outputs": [], "source": [ + "// Won't run in Xeus-cling, as it's not within the class declaration\n", + "\n", "explicit operator int() const;\n", "explicit operator double() const;" ] diff --git a/3-Operators/2-Comparison.ipynb b/3-Operators/2-Comparison.ipynb index 2a3687db252a37fdd1a124ef00a00aac8937d0a7..5b593ac8d33fbfbda6caccca5d4f71ed139a6d43 100644 --- a/3-Operators/2-Comparison.ipynb +++ b/3-Operators/2-Comparison.ipynb @@ -457,6 +457,8 @@ "metadata": {}, "outputs": [], "source": [ + "// What the operator< would look like if we wrote it ourselves:\n", + "\n", "bool operator<(const Rational& lhs, const Rational& rhs)\n", "{\n", " if !(toolbox1.Nscrewdriver_ == toolbox2.Nscrewdriver_)\n", diff --git a/3-Operators/5-Functors.ipynb b/3-Operators/5-Functors.ipynb index 0cf05ec0d72a3c321dfbaab30110bbf12e533ba8..b27f32aa7d3302c3046f8e6eeda815496f742300 100644 --- a/3-Operators/5-Functors.ipynb +++ b/3-Operators/5-Functors.ipynb @@ -81,7 +81,7 @@ " LinearFunction Double(2);\n", " LinearFunction Triple(3);\n", "\n", - " for (double value : values)\n", + " for (int value : values)\n", " std::cout << value << \"\\t\" << Double(value) << \"\\t\" << Triple(value) << std::endl;\n", "}" ] diff --git a/4-Templates/1-Intro.ipynb b/4-Templates/1-Intro.ipynb index 1cf2dbe35c5bd6157826884a9a822fa4a8e353f2..ce4ef733b57535cb717f4ccc9824a0e4c63f7afe 100644 --- a/4-Templates/1-Intro.ipynb +++ b/4-Templates/1-Intro.ipynb @@ -32,7 +32,7 @@ "metadata": {}, "outputs": [], "source": [ - "int min(int lhs, int rhs)\n", + "int Min(int lhs, int rhs)\n", "{\n", " return lhs < rhs ? lhs : rhs;\n", "}" @@ -44,7 +44,7 @@ "metadata": {}, "outputs": [], "source": [ - "double min(double lhs, double rhs)\n", + "double Min(double lhs, double rhs)\n", "{\n", " return lhs < rhs ? lhs : rhs;\n", "}" @@ -56,7 +56,7 @@ "metadata": {}, "outputs": [], "source": [ - "float min(float lhs, float rhs)\n", + "float Min(float lhs, float rhs)\n", "{\n", " return lhs < rhs ? lhs : rhs;\n", "}" @@ -71,9 +71,9 @@ "#include <iostream>\n", "\n", "{\n", - " std::cout << min(5, -8) << std::endl;\n", - " std::cout << min(5., -8.) << std::endl; \n", - " std::cout << min(5.f, -8.f) << std::endl; \n", + " std::cout << Min(5, -8) << std::endl;\n", + " std::cout << Min(5., -8.) << std::endl; \n", + " std::cout << Min(5.f, -8.f) << std::endl; \n", "}" ] }, @@ -95,7 +95,7 @@ "outputs": [], "source": [ "template<class T>\n", - "T min2(T lhs, T rhs)\n", + "T MinWithTemplate(T lhs, T rhs)\n", "{\n", " return lhs < rhs ? lhs : rhs;\n", "}" @@ -110,10 +110,10 @@ "#include <iostream>\n", "\n", "{\n", - " std::cout << min2<int>(5, -8) << std::endl;\n", - " std::cout << min2(5, -8) << std::endl; \n", - " std::cout << min2(5., -8.) << std::endl; \n", - " std::cout << min2(5.f, -8.f) << std::endl; \n", + " std::cout << MinWithTemplate<int>(5, -8) << std::endl;\n", + " std::cout << MinWithTemplate(5, -8) << std::endl; \n", + " std::cout << MinWithTemplate(5., -8.) << std::endl; \n", + " std::cout << MinWithTemplate(5.f, -8.f) << std::endl; \n", "}" ] }, @@ -306,7 +306,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The template must be reminded in the definition as well; please notice before the `::` the brackets with the parameters *without their type*.\n", + "The template must be reminded in the definition as well; please notice before the `::` the brackets with the template parameters.\n", "\n", "### Template method of a template class\n", "\n", @@ -417,7 +417,7 @@ " \n", " HoldAValue3(T value);\n", " \n", - " friend void print(const HoldAValue3<T>& obj);\n", + " friend void Print(const HoldAValue3<T>& obj);\n", " \n", " private:\n", " \n", @@ -446,7 +446,7 @@ "#include <iostream>\n", "\n", "template<class T>\n", - "void print(const HoldAValue3<T>& obj)\n", + "void Print(const HoldAValue3<T>& obj)\n", "{\n", " // Friendship required to access private data.\n", " // I wouldn't recommend friendship where an accessor would do the same task easily!\n", @@ -462,7 +462,7 @@ "source": [ "{\n", " HoldAValue3<int> hold(5);\n", - " print(hold); // LINK ERROR, and that is not something amiss in Xeus-cling!\n", + " Print(hold); // LINK ERROR, and that is not something amiss in Xeus-cling!\n", "}" ] }, @@ -489,7 +489,7 @@ " // 'Repeating' the list of template arguments and not using the ones from the class will fix the issue...\n", " // T wouldn't have work here; the label MUST differ.\n", " template<class U>\n", - " friend void print(const HoldAValue4<U>& obj);\n", + " friend void Print(const HoldAValue4<U>& obj);\n", " \n", " private:\n", " \n", @@ -519,7 +519,7 @@ "\n", "// Notice it is only a label: in the definition I'm free to use the same label as for the class definitions!\n", "template<class T>\n", - "void print(const HoldAValue4<T>& obj)\n", + "void Print(const HoldAValue4<T>& obj)\n", "{\n", " std::cout << \"Underlying value is \" << obj.value_ << std::endl;\n", "}" @@ -533,7 +533,7 @@ "source": [ "{\n", " HoldAValue4<int> hold(5);\n", - " print(hold); // Ok!\n", + " Print(hold); // Ok!\n", "}" ] }, @@ -541,7 +541,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This way of declaring friendship works but is not entirely fullproof: `print<int>` is hence a friend of `HoldAValue4<double>`, which was not what was sought. Most of the time it's ok but there are 2 other ways to declare friendship; have a look at [this link](https://web.mst.edu/~nmjxv3/articles/templates.html) if you want to learn more about it." + "This way of declaring friendship works but is not entirely fullproof: `Print<int>` is hence a friend of `HoldAValue4<double>`, which was not what was sought. Most of the time it's ok but there are 2 other ways to declare friendship; have a look at [this link](https://web.mst.edu/~nmjxv3/articles/templates.html) if you want to learn more about it." ] }, { @@ -725,4 +725,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/4-Templates/2-Specialization.ipynb b/4-Templates/2-Specialization.ipynb index 4cb0175617d32f7ab6155bc9271a1c09521da3fc..d517281fed152e51fe104af2da4234d10606e86d 100644 --- a/4-Templates/2-Specialization.ipynb +++ b/4-Templates/2-Specialization.ipynb @@ -308,9 +308,12 @@ "metadata": {}, "outputs": [], "source": [ + "#include <iostream>\n", + "\n", "template<class TypeT, std::size_t Nelts>\n", "MyArray<TypeT, Nelts>::MyArray(TypeT initial_value)\n", "{\n", + " std::cout << \"Generic constructor\" << std::endl;\n", " for (auto i = 0ul; i < Nelts; ++i)\n", " content_[i] = initial_value;\n", "}" @@ -359,7 +362,8 @@ "outputs": [], "source": [ "{\n", - " MyArray<int, 10ul> array1(2); \n", + " MyArray<int, 8ul> generic_array(2); \n", + " MyArray<int, 10ul> specific_array(2); \n", "}" ] }, @@ -408,7 +412,7 @@ "#include <string>\n", "\n", "template<class T>\n", - "void PrintSquare<T, std::string>(T t, std::string u)\n", + "void PrintSquare<T, std::string>(T t, std::string u) // COMPILATION ERROR!\n", "{\n", " std::cout << \"Partial function specialization: doesn't compile!\" << std::endl; \n", " std::cout << \"(t^2, u^2) = (\" << t * t << \", \" << (u + u) << \")\" << std::endl;\n", @@ -419,7 +423,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "You might have the impress it works if you try defining the function without specifying the brackets:" + "You might have the impress it works if you try defining the function without specifying the brackets\n", + "\n", + "_(please use [@Coliru](https://coliru.stacked-crooked.com/a/b1504e7033fd9346) - the code below no longer works in Xeus-cling as of September 2022)_" ] }, { @@ -428,48 +434,40 @@ "metadata": {}, "outputs": [], "source": [ + "// DOESN'T WORK ON XEUS-CLING!\n", + "\n", "#include <iostream>\n", "#include <string>\n", "\n", + "template<class T, class U>\n", + "void PrintSquare(T t, U u)\n", + "{\n", + " std::cout << \"Generic instantiation\" << std::endl;\n", + " std::cout << \"(t^2, u^2) = (\" << t * t << \", \" << u * u << \")\" << std::endl;\n", + "}\n", + "\n", + "\n", "template<class T>\n", "void PrintSquare(T t, std::string u)\n", "{\n", - " std::cout << \"Seamingly ok function template specialization \" << std::endl;\n", + " std::cout << \"Seemingly ok function template specialization \" << std::endl;\n", " std::cout << \"(t^2, u^2) = (\" << t * t << \", \" << (u + u) << \")\" << std::endl;\n", - "}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "// May fail to Xeus-cling; but expected behaviour is to 'work' but not as intended...\n", - "// Restarting your kernel may make it work (of course then execute first the cell above)\n", + "}\n", + "\n", + "\n", + "int main(int argc, char** argv)\n", "{\n", " std::string hello(\"Hello\");\n", " PrintSquare(5., hello);\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "but you actually did simply an overload of `Print()`; calling the explicit specialization fails:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "scrolled": true - }, - "outputs": [], - "source": [ - "{\n", - " PrintSquare<double, std::string>(5., std::string(\"Hello\"));\n", + " \n", + " // But if you uncomment this line, the compilation will fail...\n", + " // PrintSquare<double, std::string>(5., std::string(\"Hello\"));\n", + " \n", + " // ... whereas it is a perfectly legit rule of instantiation, as you may check with:\n", + " // PrintSquare<double, int>(5., 3);\n", + " \n", + " \n", + " return EXIT_SUCCESS; \n", "}" ] }, diff --git a/4-Templates/3-Syntax.ipynb b/4-Templates/3-Syntax.ipynb index c6bf782cd2699eaf0c63782a7eda5fba80e223e5..96610e517462e6410e989b86416f662e0d09c0ee 100644 --- a/4-Templates/3-Syntax.ipynb +++ b/4-Templates/3-Syntax.ipynb @@ -240,7 +240,7 @@ "#include <list>\n", "\n", "template<typename T>\n", - "concept HasValueType = requires(T a) \n", + "concept HasValueType = requires(T) \n", "{\n", " typename T::value_type;\n", "};\n", diff --git a/4-Templates/5-MoreAdvanced.ipynb b/4-Templates/5-MoreAdvanced.ipynb index 9a69eae9a752d9ea0564d6fb08127628c317cbec..8c8ff28b6ba29eafe084947dd4fc2871262b8058 100644 --- a/4-Templates/5-MoreAdvanced.ipynb +++ b/4-Templates/5-MoreAdvanced.ipynb @@ -114,7 +114,7 @@ "- Requires `T` is copyable.\n", "- Copy `T`, which is potentially a time-consuming operator. \n", "\n", - "We could replace by `const T& GetValue() const`, but it's a bit on the nose (and less efficient) for plain old data type. The best of both world may be achieved by a trait:\n" + "We could replace by `const T& GetValue() const` to solve both those issues, but it's a bit on the nose (and less efficient) for plain old data type. The best of both world may be achieved by a trait:\n" ] }, { @@ -125,6 +125,8 @@ "source": [ "#include <iostream>\n", "#include <string>\n", + "#include <type_traits> // for std::conditional, std::is_trivial\n", + "\n", "\n", "template<class T>\n", "class ImprovedHoldAValue\n", diff --git a/5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb b/5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb index 396250402cf40192e8fdd232f1e632f6b2a056ff..a3600d3597e84b7a25d56dc69b7a5789ece9f331 100644 --- a/5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb +++ b/5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb @@ -27,7 +27,7 @@ "\n", "## Compiler warnings and errors\n", "\n", - "The first way to find out possible errors are during compilation time: you may ensure your code is correct by making its compilation fails if not. There are many ways to do so, even more so if templates are involved; here are few of them we have already seen:\n", + "The first way to find out possible errors are during compilation time: you may ensure your code is correct by making its compilation fails if not (that's exactly the spirit of the example we provided for [template template parameter](../4-Templates/5-MoreAdvanced.ipynb#Template-template-parameters-(not-a-mistake...)). There are many ways to do so, even more so if templates are involved; here are few of them we have already seen:\n", "\n", "* `static_assert` we saw in [template introduction](../4-Templates/1-Intro.ipynb#static_assert)\n", "* Duck typing failure: if a template argument used somewhere doesn't comply with the expected API.\n", @@ -71,7 +71,7 @@ "\n", "So you may get extensive tests in debug mode that are ignored once your code has been thoroughly checked and is ready for production use (_debug_ and _release_ mode will be explained in a [later notebook](../6-InRealEnvironment/3-Compilers.ipynb#Debug-and-release-flags)).\n", "\n", - "The example above is a very useful use: before dereferencing a pointer check it is not `nullptr` (hence the good practice to always initialize a pointer to `nullptr`...)\n", + "The example above is a very useful use: before dereferencing a pointer checks it is not `nullptr` (hence the good practice to always initialize a pointer to `nullptr`...)\n", "\n", "In **release mode**, the macro `NDEBUG` should be defined and all the `assert` declarations will be ignored by the compiler.\n", "\n", @@ -97,7 +97,7 @@ "source": [ "Asserts are clearly a **developer** tool: they are there to signal something does not behave as intended and therefore that there is a bug somewhere...\n", "\n", - "However, they are clearly not appropriate to handle an error of your program end-user: for instance if he specifies an invalid input file, you do not want an abort which is moreover handled only in debug mode!\n", + "However, they are clearly not appropriate to handle an error of your program end-user: for instance if he specifies an invalid input file, you do not want an `abort` which is moreover handled only in debug mode!\n", "\n", "### `throw`\n", "\n", @@ -178,9 +178,9 @@ " catch(int n)\n", " {\n", " if (n == 1)\n", - " std::cout << \"Error: value is bigger than 9!\" << std::endl;\n", + " std::cerr << \"Error: value is bigger than 9!\" << std::endl;\n", " if (n == -1)\n", - " std::cout << \"Error: value is less than -9!\" << std::endl;\n", + " std::cerr << \"Error: value is less than -9!\" << std::endl;\n", " }\n", " std::cout << \"End\" << std::endl;\n", "}" @@ -206,11 +206,11 @@ " }\n", " catch(float n) // doesn't catch your `int` exception!\n", " {\n", - " std::cout << \"Float case: \" << n << \" was provided and is not an integer\" << std::endl;\n", + " std::cerr << \"Float case: \" << n << \" was provided and is not an integer\" << std::endl;\n", " }\n", " catch(...)\n", " {\n", - " std::cout << \"Gluttony case... but no object to manipulate to extract more information!\" << std::endl;\n", + " std::cerr << \"Gluttony case... but no object to manipulate to extract more information!\" << std::endl;\n", " }\n", "}" ] @@ -221,7 +221,7 @@ "source": [ "### Re-throw\n", "\n", - "Once an exception has been caught by a `catch` block, it is considered to be handled; the code will therefore go on to what is immediately after the block. If you want to throw the exception again (for instance afterloggin a message) you may just type `throw`:" + "Once an exception has been caught by a `catch` block, it is considered to be handled; the code will therefore go on to what is immediately after the block. If you want to throw the exception again (for instance after logging a message) you may just type `throw`:" ] }, { @@ -238,7 +238,7 @@ " }\n", " catch(int n)\n", " {\n", - " std::cout << \"Int case: \" << n << \" not a single digit number\" << std::endl;\n", + " std::cerr << \"Int case: \" << n << \" not a single digit number\" << std::endl;\n", " }\n", " \n", " std::cout << \"After catch\" << std::endl;\n", @@ -259,7 +259,7 @@ " }\n", " catch(int n)\n", " {\n", - " std::cout << \"Int case: \" << n << \" not a single digit number\" << std::endl;\n", + " std::cerr << \"Int case: \" << n << \" not a single digit number\" << std::endl;\n", " throw; // `throw n` would have been correct as well but is not necessary\n", " }\n", " \n", @@ -345,7 +345,7 @@ " }\n", " catch(const std::exception& e)\n", " {\n", - " std::cout << \"Properly caught: \" << e.what() << std::endl;\n", + " std::cerr << \"Properly caught: \" << e.what() << std::endl;\n", " }\n", "}" ] @@ -365,7 +365,7 @@ "metadata": {}, "outputs": [], "source": [ - "// Does not run in Xeus-cling!\n", + "// Pseudo-code - Do not run in Xeus-cling!\n", "\n", "try\n", "{\n", @@ -396,6 +396,8 @@ "* It is rather time consuming: defining a specific exception means a bit of boilerplate to write, and those minutes might have been spent more efficiently, as...\n", "* Most of the time, you don't even need the filtering capacity; in my code for instance if an exception is thrown it is 99 % of the time to be caught in the `main()` function to terminate properly the execution.\n", "\n", + "The only case in which it might be very valuable to use a tailored exception is for your integration tests: if you are writing a test in which an exception is expected, it is better to check the exception you caught is exactly the one that was expected and not a completely unrelated exception which was thrown for another reason.\n", + "\n", "STL provides many derived class from `std::exception` which you might use directly or as base of your own class; see [cppreference](https://en.cppreference.com/w/cpp/error/exception) for more details. [OpenClassrooms](https://openclassrooms.com/fr/courses/1894236-programmez-avec-le-langage-c/1903837-gerez-des-erreurs-avec-les-exceptions) (which despite its name is in french) sorted out the go-to exceptions for lazy developers which cover most of the cases (don't get me wrong: laziness is often an asset for a software developer!):\n", "\n", "* `std::domain_error`\n", @@ -513,13 +515,20 @@ "}" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It should be noticed C++ 11 introduced a dedicated class to handle more gracefully error codes: [`std::error_code`](https://en.cppreference.com/w/cpp/error/error_code). I have no direct experience with it but it looks promising as illustrated by this [blog post](https://akrzemi1.wordpress.com/2017/07/12/your-own-error-code/)." + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ "### nodiscard\n", "\n", - "The latter point may however be mitigated since C++17 with the attribute [nodiscard](https://en.cppreference.com/w/cpp/language/attributes/nodiscard), which helps your compiler figure out the return value should have been checked." + "The point about forgetting to check the value may however be mitigated since C++17 with the attribute [``nodiscard``](https://en.cppreference.com/w/cpp/language/attributes/nodiscard), which helps your compiler figure out the return value should have been checked." ] }, { @@ -637,5 +646,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/5-UsefulConceptsAndSTL/2-RAII.ipynb b/5-UsefulConceptsAndSTL/2-RAII.ipynb index 2d3c17e3aa2d3a2576c74dab4a8b655969b305ac..6e3a7031c910a54aa4ba9bbf6d8c12e1d72d9ea3 100644 --- a/5-UsefulConceptsAndSTL/2-RAII.ipynb +++ b/5-UsefulConceptsAndSTL/2-RAII.ipynb @@ -31,7 +31,7 @@ "\n", "C++ provides in fact the best of both worlds: a way to provide safe freeing of memory as soon as possible... provided you know how to adequately use it.\n", "\n", - "The **Ressource Acquisition Is Initialization** or **RAII** is the key mechanism for this: the idea is just to use an object with:\n", + "The **Ressource Acquisition Is Initialization** or **RAII** idiom is the key mechanism for this; the idea is just to use an object with:\n", "* The constructor in charge of allocating the ressources (memory, mutexes, etc...)\n", "* The destructor in charge of freeing all that as soon as the object becomes out-of-scope.\n", "\n", @@ -191,5 +191,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/5-UsefulConceptsAndSTL/3-Containers.ipynb b/5-UsefulConceptsAndSTL/3-Containers.ipynb index 2bdd15cd252fadfa1ff7337ce0d82107c51cde5f..426ca33cb41217a27ceb14da5ded59d7a051a3fc 100644 --- a/5-UsefulConceptsAndSTL/3-Containers.ipynb +++ b/5-UsefulConceptsAndSTL/3-Containers.ipynb @@ -387,34 +387,17 @@ "#include <iostream>\n", "#include <string>\n", "\n", - "// Utility to print the content of a non-associative container.\n", - "// Don't bother with it now: it uses up iterators we'll see a bit below.\n", - "template\n", - "<\n", - " class VectorT\n", - ">\n", - "void PrintVector(const VectorT& vector,\n", - " std::string separator = \", \", std::string opener = \"[\", std::string closer = \"]\\n\")\n", + "// Helper function to avoid typing endlessly the same lines...\n", + "template<class VectorT>\n", + "void PrintVector(const VectorT& vector)\n", "{\n", " auto size = vector.size();\n", - " std::cout << \"Size = \" << size << \" Capacity = \" << vector.capacity() << \" Content = \";\n", - " std::cout << opener;\n", - "\n", - " auto it = vector.cbegin();\n", - " auto end = vector.cend();\n", + " std::cout << \"Size = \" << size << \" Capacity = \" << vector.capacity() << \" Content = [ \";\n", " \n", - " static_cast<void>(end); // to avoid compilation warning in release mode\n", - "\n", - " for (decltype(size) i = 0u; i + 1u < size; ++it, ++i)\n", - " {\n", - " assert(it != end);\n", - " std::cout << *it << separator;\n", - " }\n", + " for (auto item : vector)\n", + " std::cout << item << ' ';\n", "\n", - " if (size > 0u)\n", - " std::cout << *it;\n", - "\n", - " std::cout << closer;\n", + " std::cout << ']' << std::endl;\n", "}" ] }, @@ -433,6 +416,11 @@ " foo.resize(8, 10); // Second optional argument gives the values to add.\n", " PrintVector(foo);\n", " \n", + " foo.resize(12); // If not specified, a default value is used - here 0 for a POD\n", + " // The default value is the same as the one that would be used when constructing\n", + " // an element with empty braces - here `std::size_t myvariable {}`;\n", + " PrintVector(foo);\n", + " \n", " foo.resize(3, 15);\n", " PrintVector(foo);\n", "}" @@ -500,8 +488,7 @@ " five_pi_digits.push_back(1);\n", " five_pi_digits.push_back(5);\n", " \n", - " for (auto item : five_pi_digits)\n", - " std::cout << \"Digit = \" << item << std::endl; // Not what we intended!\n", + " PrintVector(five_pi_digits); // not what we intended!\n", "}\n" ] }, @@ -611,9 +598,6 @@ "source": [ "It may not extremely efficient: at each call to `operator[]`, the program must figure out the element to draw without using the fact it had just fetched the element just in the previous memory location (in practice now compilers are rather smart and figure this out...)\n", "\n", - "**[Update]** An engineer was intrigued by this and did a benchmark... And there was no difference: compilers seem now clever enough to optimize this on their own!\n", - "\n", - "\n", "Iterators provides this (possibly) more efficient access:" ] }, @@ -641,7 +625,7 @@ "source": [ "It is more efficient and quite verbosy; prior to C++ 11 you had to use this nonetheless (just `auto` would simplify greatly the syntax here but it is also a C++11 addition...)\n", "\n", - "Iterators are *not* pointers, even if they behave really similarly, e.g. they may use the same `*` and `->` syntax (they might be implemented as pointers, but think of it as private inheritance in this case...)\n", + "Iterators are *not* pointers, even if they behave really similarly, e.g. they may use the same `*` and `->` syntax (they might be implemented as pointers, but think of it as [private inheritance](../2-ObjectProgramming/6-inheritance.ipynb#IS-IMPLEMENTED-IN-TERMS-OF-relationship-of-private-inheritance) in this case...)\n", "\n", "There are several flavors:\n", "* Constant iterators, used here, with which you can only read the value under the iterator.\n", @@ -687,7 +671,7 @@ "source": [ "is undefined behaviour: it might work (seems to in this notebook) but is not robust. Here it \"works\" but you may see the iteration is done over the initial vector; the additional values aren't iterated over (we would end up with an infinite loop in this case).\n", "\n", - "So, the bottom line is you should really separate actions that modify the structure of a container and iteration over it.\n", + "So, the bottom line is you should really separate actions that modify the structure of a container and iterate over it.\n", "\n", "## Incrementing / decrementing iterators\n", "\n", @@ -739,7 +723,7 @@ "\n", "* `std::list`: A double-linked list: the idea is that each element knows the addresses of the element before and the element after. It might be considered if you need to add often elements at specific locations in the list: adding a new element is just changing 2 pointers and setting 2 new ones. You can't access directly an element by its index with a `std::list`.\n", "* `std::slist`: A single-linked list: similar as a `std::list` except only the pointer to the next element is kept.\n", - "* `std::deque`: For \"double ended queue\"; this container may be helpful if you need to store a really huge amount of data that might not fit within a `std::vector`. It might also be of use if you are to add often elements in front on the list: there is `push_front()` method as well as a `push_back` one. Item 18 of \\cite{Meyers2001} recommends using `std::deque` with `bool`: `std::vector<bool>` was an experiment to provide a specific implementation to spare memory that went wrong and should therefore be avoided...\n", + "* `std::deque`: For \"double ended queue\"; this container may be helpful if you need to store a really huge amount of data that might not fit within a `std::vector`. It might also be of use if you are to add often elements in front on the list: there is `push_front()` method as well as a `push_back` one. Item 18 of \\cite{Meyers2001} recommends using `std::deque` with `bool`: `std::vector<bool>` was an experiment to provide a specific implementation to spare memory when storing booleans that went wrong and should therefore be avoided...\n", "* `std::array`: You should use this one if the number of elements is known at compile time and doesn't change at all.\n", "* `std::string`: Yes, it is actually a container! I will not tell much more about it; just add that it is the sole container besides `std::vector` that ensures continuous storage." ] @@ -811,5 +795,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/5-UsefulConceptsAndSTL/4-AssociativeContainers.ipynb b/5-UsefulConceptsAndSTL/4-AssociativeContainers.ipynb index 7f7adb79e9b862fbe29b98fa4ae7f4fbb5f38158..427b472c7b38ce52a32cf6fa0f16dea2b4a45cd5 100644 --- a/5-UsefulConceptsAndSTL/4-AssociativeContainers.ipynb +++ b/5-UsefulConceptsAndSTL/4-AssociativeContainers.ipynb @@ -129,7 +129,7 @@ "source": [ "#### C++ 17: Structure binding\n", "\n", - "C++ 17 introduced an alternate new syntax is like a lot that is called **structure bindings**:" + "C++ 17 introduced an alternate new syntax I like a lot that is called **structure bindings**:" ] }, { @@ -146,8 +146,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As you see, the syntax allocates on the fly variable (here references) for the first and second element of the pair.\n", - "\n", + "As you see, the syntax allocates on the fly variable (here references) for the first and second element of the pair, making the code much more expressive.\n", "You may read more on them [here](https://www.fluentcpp.com/2018/06/19/3-simple-c17-features-that-will-make-your-code-simpler/); we will use them again in this notebook." ] }, @@ -221,7 +220,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "See here that David was correctly inserted... but Alice was unchanged! \n", + "See here that Dave was correctly inserted... but Alice was unchanged! \n", "\n", "In fact `insert` returns a pair:\n", "* First is an iterator to the newly inserted element, or to the position of the one that made the insertion fail.\n", @@ -274,6 +273,8 @@ "metadata": {}, "outputs": [], "source": [ + "// In 2022 seems to fail with Xeus-Cling, but it is perfectly code I heartily recommend over the more clunky notation above.\n", + "\n", "const auto& [iterator, was_properly_inserted] = age_list.insert({\"Alice\", 32});\n", "if (!was_properly_inserted)\n", " std::cerr << \"Insertion of Alice failed\" << std::endl;" @@ -283,7 +284,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "That's something I dislike in this very useful class: error handling is not up to my taste as you have to remember to check explicitly all went right...\n", + "That's something I dislike in this very useful class: error handling is not up to my taste as you have to remember to check explicitly all went right... (this is the discussion we had previously about error codes all over again...)\n", "\n", "### Access to one element: don't use `operator[]`!\n", "\n", @@ -319,7 +320,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "So if you provide a wrong key, it doesn't yell and create on the spot a new entry, filling the associated value with the default constructor for the type...\n", + "So if you provide a wrong key, it doesn't yell and create instead on the spot a new entry, filling the associated value with the default constructor for the type...\n", "\n", "To do it properly (but more verbose!), use the `find()` method (if you're intrigued by the use of iterator there, we will present them more in details in the notebook about [algorithms](./7-Algorithms.ipynb)):" ] @@ -387,7 +388,7 @@ "\n", "You may use your own objects as keys, provided that:\n", "\n", - "* Either you define `operator<` for it. `operator==` doesn't matter: even in `find` it is really `operator<` that is used!\n", + "* Either you define `operator<` for it. It is really important to grasp that `operator==` doesn't matter: even in `find` it is really `operator<` that is used!\n", "* Or provide as template parameter the ordering relationship you intend to use.\n", "\n", "**WARNING:** If you're using pointers as keys, make sure to provide an adequate relationship ordering, typically that takes the pointed object relationship. Otherwise from one run to another you might end with different results as the address won't probably be given in the same order..." @@ -410,7 +411,7 @@ "* Access is much more efficient (~O(1), i.e. independant on the number of elements!)\n", "* Memory imprint is bigger.\n", "* Adding new elements is more expensive.\n", - "* The result is not ordered, and there are no rules whatsoever: two runs might not yield the list in the same order.\n", + "* The result is not ordered, and there are no rules whatsoever: two runs on the same computer might not yield the list in the same order.\n", "\n", "The constraint of the key is different too: the key must be **hashable**: there must be a specialization of `std::hash` for the type used for key. It must also define `operator==`.\n", "\n", @@ -432,7 +433,6 @@ "{\n", " std::unordered_map<int, double> list;\n", " list.max_load_factor(0.7f);\n", - " \n", "}" ] }, diff --git a/5-UsefulConceptsAndSTL/5-MoveSemantics.ipynb b/5-UsefulConceptsAndSTL/5-MoveSemantics.ipynb index bbc9d78d0135365573f78fecd3ae8e76ed0aede6..298c4ff7d25046acc89c7753e759dbb4223dc818 100644 --- a/5-UsefulConceptsAndSTL/5-MoveSemantics.ipynb +++ b/5-UsefulConceptsAndSTL/5-MoveSemantics.ipynb @@ -44,7 +44,7 @@ " public :\n", " \n", " // For next section - don't bother yet\n", - " friend void swap(Text& lhs, Text& rhs);\n", + " friend void Swap(Text& lhs, Text& rhs);\n", "\n", " Text(const char* string);\n", " \n", @@ -89,7 +89,7 @@ "source": [ "Text::Text(const char* string)\n", "{\n", - " std::cout << \"Constructor called\" << std::endl;\n", + " std::cout << \"Constructor called with argument '\" << string << \"'\" << std::endl;\n", " size_ = std::strlen(string) + 1;\n", " data_ = new char[size_] ;\n", " std::copy(string, string + size_, data_);\n", @@ -148,7 +148,7 @@ "source": [ "## A traditional answer: to allow the exchange of internal data\n", "\n", - "By allowing two texts to exchange (swap) their internal data, we can rewrite our program in a much more economical way in terms of execution time:" + "By allowing two `Text` objects to exchange (swap) their internal data, we can rewrite our program in a much more economical way in terms of execution time, by leveraging the fact we know the internal structure of the class:" ] }, { @@ -157,7 +157,7 @@ "metadata": {}, "outputs": [], "source": [ - "void swap(Text& lhs, Text& rhs)\n", + "void Swap(Text& lhs, Text& rhs)\n", "{ \n", " unsigned int tmp_size = lhs.size_;\n", " char* tmp_data = lhs.data_;\n", @@ -180,7 +180,7 @@ " Text t2(\"Hello\") ;\n", " \n", " // Swap of values:\n", - " swap(t1, t2);\n", + " Swap(t1, t2);\n", " \n", " std::cout << t1 << \" \" << t2 << std::endl;\n", "}" @@ -243,7 +243,7 @@ "source": [ "#include <iostream>\n", "\n", - "void print(std::string& lvalue)\n", + "void Print(std::string& lvalue)\n", "{\n", " std::cout << \"l-value is \" << lvalue << std::endl;\n", "}" @@ -256,7 +256,7 @@ "outputs": [], "source": [ "{\n", - " print(\"hello\") ; // Compilation error: \"hello\" is a r-value!\n", + " Print(\"hello\") ; // Compilation error: \"hello\" is a r-value!\n", "}" ] }, @@ -276,7 +276,7 @@ "#include <iostream>\n", "#include <string>\n", "\n", - "void print_by_copy(std::string value) // no reference here!\n", + "void PrintByCopy(std::string value) // no reference here!\n", "{\n", " std::cout << \"l- or r- value is \" << value << std::endl;\n", "}" @@ -289,7 +289,7 @@ "outputs": [], "source": [ "{\n", - " print_by_copy(\"hello\") ; // Ok!\n", + " PrintByCopy(\"hello\") ; // Ok!\n", "}" ] }, @@ -306,7 +306,7 @@ "metadata": {}, "outputs": [], "source": [ - "void print_by_const_ref(const std::string& lvalue) \n", + "void PrintByConstRef(const std::string& lvalue) \n", "{\n", " std::cout << \"l-value is \" << lvalue << std::endl;\n", "}" @@ -319,7 +319,7 @@ "outputs": [], "source": [ "{\n", - " print_by_const_ref(\"hello\") ; // Ok!\n", + " PrintByConstRef(\"hello\") ; // Ok!\n", "}" ] }, @@ -395,16 +395,16 @@ "#include <iostream>\n", "#include <vector>\n", "\n", - "void print_double(const std::vector<int>& vec)\n", + "void PrintDouble(const std::vector<int>& vec)\n", "{\n", - " std::cout << \"print_double for l-value\" << std::endl;\n", + " std::cout << \"PrintDouble for l-value\" << std::endl;\n", " std::vector<int> copy(vec);\n", " \n", " for (auto& item : copy)\n", " item *= 2;\n", " \n", " for (auto item : copy)\n", - " std::cout << item << \"\\t\";\n", + " std::cout << item << ' ';\n", " \n", " std::cout << std::endl;\n", "}" @@ -418,7 +418,7 @@ "source": [ "{\n", " std::vector<int> primes { 2, 3, 5, 7, 11, 13, 17, 19 }; \n", - " print_double(primes);\n", + " PrintDouble(primes);\n", "}" ] }, @@ -437,14 +437,14 @@ "source": [ "#include <iostream>\n", "\n", - "void print_double(std::vector<int>&& vec)\n", + "void PrintDouble(std::vector<int>&& vec)\n", "{\n", - " std::cout << \"print_double for r-value\" << std::endl;\n", + " std::cout << \"PrintDouble for r-value\" << std::endl;\n", " for (auto& item : vec)\n", " item *= 2;\n", " \n", " for (auto item : vec)\n", - " std::cout << item << \"\\t\";\n", + " std::cout << item << ' ';\n", " \n", " std::cout << std::endl;\n", "}" @@ -457,7 +457,35 @@ "outputs": [], "source": [ "{\n", - " print_double(std::vector<int>{ 2, 3, 5, 7, 11, 13, 17, 19 });\n", + " PrintDouble(std::vector<int>{ 2, 3, 5, 7, 11, 13, 17, 19 });\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "But if used it upon a `std::vector` that can't be modified, we can fall back to the other instantiations which doesn't harm the content of the vector:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "{\n", + " std::vector<int> initial_vector{ 2, 3, 5, 7, 11, 13, 17, 19 };\n", + " PrintDouble(initial_vector);\n", + " \n", + " std::cout << \"And initial vector is properly unchanged: [ \";\n", + " \n", + " for (const auto item : initial_vector)\n", + " std::cout << item << ' ';\n", + " \n", + " std::cout << \"]\" << std::endl;\n", + " \n", + " \n", "}" ] }, @@ -478,7 +506,7 @@ "source": [ "{\n", " std::vector<int> primes { 2, 3, 5, 7, 11, 13, 17, 19 }; \n", - " print_double(static_cast<std::vector<int>&&>(primes));\n", + " PrintDouble(static_cast<std::vector<int>&&>(primes));\n", "}" ] }, @@ -499,8 +527,8 @@ "source": [ "{\n", " std::vector<int> primes { 2, 3, 5, 7, 11, 13, 17, 19 }; \n", - " print_double(std::move(primes)); // strictly equivalent to the static_cast in former cell!\n", - "}\n" + " PrintDouble(std::move(primes)); // strictly equivalent to the static_cast in former cell!\n", + "}" ] }, { @@ -647,7 +675,7 @@ "metadata": {}, "outputs": [], "source": [ - "// Snippet not complete complete enough to work.\n", + "// Snippet not complete enough to work.\n", "\n", "class Matrix; // forward declaration - don't bother yet!\n", "\n", @@ -691,8 +719,6 @@ "{\n", " public :\n", " \n", - " friend void swap(Text2& lhs, Text2& rhs);\n", - "\n", " Text2(const char* string);\n", " \n", " // Copy constructor.\n", @@ -828,7 +854,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "With all this move semantics, the operations above are comparable to what we achieved with the `swap` function for `Text` earlier... with the additional benefit that this semantic is not only used for swapping two values.\n", + "With all this move semantics, the operations above are comparable to what we achieved with the `Swap` function for `Text` earlier... with the additional benefit that this semantic is not only used for swapping two values.\n", "\n", "As already mentioned [there](../3-Operators/4-CanonicalForm.ipynb#[Advanced]-The-true-canonical-class), there are specific rules called __Rule of 0__, __Rule of 3__ and __Rule of 5__, which explains which constructor(s), destructor and assigmnent operator you ought to define for your class.\n", "\n", @@ -847,7 +873,7 @@ "#include <iostream>\n", "#include <string>\n", "\n", - "void do_stuff(std::string&& string)\n", + "void DoStuff(std::string&& string)\n", "{ \n", " std::cout << \"Argument given by r-value is: \" << string << std::endl;\n", " string = \"Bye!\";\n", @@ -862,7 +888,7 @@ "outputs": [], "source": [ "{\n", - " do_stuff(\"Hello!\");\n", + " DoStuff(\"Hello!\");\n", "}" ] }, @@ -928,7 +954,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Unfortunately, C++ 11 committee didn't give a name to this specific call; Scott Meyers first publicized it under the name **universal reference**... and was not followed by the C++ committee that finally chose **forwarding reference**. You may therefore find one or the other term, but the idea behind is exactly the same." + "Unfortunately, C++ 11 committee didn't give immediately a name to this specific call; Scott Meyers first publicized it under the name **universal reference**... and was not followed by the C++ committee that finally chose **forwarding reference**. You may therefore find one or the other term, but the idea behind is exactly the same." ] }, { @@ -999,5 +1025,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/5-UsefulConceptsAndSTL/6-SmartPointers.ipynb b/5-UsefulConceptsAndSTL/6-SmartPointers.ipynb index 511f373cc2a50290d11e21910087feab0865ac32..9ce068b6709ffcc2283a30292e95589b8414e76c 100644 --- a/5-UsefulConceptsAndSTL/6-SmartPointers.ipynb +++ b/5-UsefulConceptsAndSTL/6-SmartPointers.ipynb @@ -45,7 +45,7 @@ "\n", "Smart pointers are clearly a very good way to handle the ownership of a given object. \n", "\n", - "This does not mean they supersede entirely ordinary (often called **raw** or **dumb**) pointers: raw pointers might be a good choice to pass an object as a function parameter (see the discussion for the third question in this [Herb Sutter's post blog](https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/)). The raw pointer behind a smart pointer may be accessed through the `get()` method.\n", + "This does not mean they supersede entirely ordinary (often called **raw** or more infrequently **dumb**) pointers: raw pointers might be a good choice to pass an object as a function parameter (see the discussion for the third question in this [Herb Sutter's post blog](https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/)). The raw pointer behind a smart pointer may be accessed through the `get()` method.\n", "\n", "Both smart pointers exposed below may be constructed directly from a raw pointer; in this case they take the responsability of destroying the pointer:" ] @@ -138,11 +138,10 @@ "\n", "{\n", " auto ptr = std::make_unique<int>(5);\n", - " auto copy = std::move(ptr); \n", - " \n", - "// std::cout << \"Beware as now there are no guarantee upon the content of ptr: \" << *ptr << std::endl;\n", - "// < This line is invalid (using `ptr` after move is undefined behaviour) and makes Xeus-cling crash\n", + " auto copy = std::move(ptr);\n", " \n", + " // std::cout << \"Beware as now there are no guarantee upon the content of ptr: \" << *ptr << std::endl;\n", + " // < This line is invalid (using `ptr` after move is undefined behaviour) and makes Xeus-cling crash\n", "}" ] }, @@ -171,9 +170,8 @@ " public:\n", " \n", " Content(std::string&& text); // notice: no default constructor!\n", - " \n", - " \n", - " const std::string& GetValue() const;\n", + " \n", + " const std::string& GetValue() const;\n", " \n", " private:\n", " \n", @@ -376,7 +374,7 @@ "\n", "`shared_ptr` are clearly useful, but you should always wonder first if you really need them: for most uses a `unique_ptr` eventually seconded by raw pointers extracted by `get()` is enough.\n", "\n", - "There is also a risk of not releasing properly the memory is there is a circular dependancy between two `shared_ptr`. A variation of this pointer named `weak_ptr` enables to circumvent this issue, but is a bit tedious to put into motion. I have written in [appendix](../7-Appendix/WeakPtr.ipynb) to describe how to do so.\n", + "There is also a risk of not releasing properly the memory is there is a circular dependancy between two `shared_ptr`. A variation of this pointer named `weak_ptr` enables to circumvent this issue, but is a bit tedious to put into motion. I have written in [appendix](../7-Appendix/WeakPtr.ipynb) a notebook to describe how to do so.\n", "\n" ] }, @@ -628,5 +626,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/5-UsefulConceptsAndSTL/7-Algorithms.ipynb b/5-UsefulConceptsAndSTL/7-Algorithms.ipynb index fb32ad6d4d23ef72fd8b1c5e54a261f80f098d32..0424809048c7cd49a9fca95a1d0ff88438bbd283 100644 --- a/5-UsefulConceptsAndSTL/7-Algorithms.ipynb +++ b/5-UsefulConceptsAndSTL/7-Algorithms.ipynb @@ -29,7 +29,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Even if C++ can't be qualified as a _batteries included_ language like Python (until C++ 17 there was no proper filesystem management, and the support of this feature was still shaky at best in several STL implementations one year ago...), there are plenty of algorithms that are already provided within the STL.\n", + "Even if C++ can't be qualified as a _batteries included_ language like Python (until C++ 17 there was no proper filesystem management, and the support of this feature was still shaky at best in several STL implementations circa 2019...), there are plenty of algorithms that are already provided within the STL.\n", "\n", "We won't obviously list them all here - the mighty \\cite{Josuttis2012} which is more than 1000 pages long don't do it either! - but show few examples on how to use them. For instance, many STL algorithms rely upon iterators: this way a same algorithm may be used as well on `std::vector`, `std::list`, and so on...\n", "\n", @@ -273,7 +273,7 @@ "source": [ "The issue is that the memory is not allocated first: the algorithm doesn't provide the memory at destination! (the reason is that an algorithm is as generic as possible; here `std::copy_if` is expected to work as well with `std::set`... and `std::vector` and `std::set` don't use the same API to allocate the memory).\n", "\n", - "Of course, in some cases it is tricky to know in advance what you need, and here computing it previously with `std::count_if` add an additional operation. There is actually a way to tell the program to insert the values by `push_back` with `std::back_inserter`; it might be a good idea to reserve enough memory to use this method without recopy:" + "Of course, in some cases it is tricky to know in advance what you need, and here computing it previously with `std::count_if` adds an additional operation. There is actually a way to tell the program to insert the values by `push_back` with `std::back_inserter`; it might be a good idea to reserve enough memory to use this method without recopy:" ] }, { @@ -479,7 +479,7 @@ " \n", " std::cout << \"The even values are: \";\n", " \n", - " for (auto it = int_vec.cbegin(); it != logical_end; ++it)\n", + " for (auto it = int_vec.cbegin(); it != logical_end; ++it) // see the use of `logical_end` here!\n", " std::cout << *it << \" \";\n", " \n", " std::cout << std::endl;\n", @@ -541,7 +541,7 @@ "\n", "It is also important to highlight that while the STL algorithms may provide you efficiency (this library is written by highly skilled engineers after all), this is not its main draw: the algorithms are written to be as generic as possible. The primary reason to use them is to allow you to think at a higher level of abstraction, not to get the fastest possible implementation. So if your ~~intuition~~ benchmarking has shown that the standard library is causing a critical slowdown, you are free to explore classic alternatives such as [loop unrolling](https://en.wikipedia.org/wiki/Loop_unrolling) - that's one of the strength of the language (and the STL itself opens up this possibility directly for some of its construct - you may for instance use your own memory allocator when defining a container). For most purposes however that will not be necessary.\n", "\n", - "FYI, C++ 20 introduces a completely new way to deal with algorithms, which does not rely on direct use of iterators but instead on a range library. This leads to a syntax which is more akin to what is done in other languages - see for instance this example lifted from this [blog post](https://www.modernescpp.com/index.php/c-20-the-ranges-library):\n" + "FYI, C++ 20 introduces a completely new way to deal with algorithms, which does not rely on direct use of iterators but instead on a range library. This leads to a syntax which is more akin to what is done in other languages - see for instance this example [@Coliru](https://coliru.stacked-crooked.com/a/efbfb359b4dfa6ee):" ] }, { @@ -559,12 +559,13 @@ "int main(int argc, char** argv) \n", "{\n", "\n", - " std::vector<int> numbers = {1, 2, 3, 4, 5, 6};\n", + " std::vector<int> numbers = { 3, 5, 12, 17, 21, 27, 28 };\n", " \n", - " auto results = numbers | std::views::filter([](int n){ return n % 2 == 0; })\n", - " | std::views::transform([](int n){ return n * 2; });\n", + " auto results = numbers | std::views::filter([](int n){ return n % 3 == 0; })\n", + " | std::views::transform([](int n){ return n / 3; });\n", " \n", - " for (auto v: results) std::cout << v << \" \"; // 4 8 12\n", + " for (auto v: results) \n", + " std::cout << v << \" \"; // 1 4 7 9\n", "\n", " return EXIT_SUCCESS;\n", "}" @@ -574,7 +575,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Having no first hand experience of it I really can't say more about it but don't be astonished if you meet such a syntax in a C++ program." + "Having no first hand experience of it I really can't say more about it but don't be astonished if you meet such a syntax in a C++ program; you may learn a bit more for instance [here](https://www.modernescpp.com/index.php/c-20-the-ranges-library)." ] }, { diff --git a/6-InRealEnvironment/1-SetUpEnvironment.ipynb b/6-InRealEnvironment/1-SetUpEnvironment.ipynb index 3b6a8f860eabc7f20740d0649e135b0127b7c3a2..3c3b9e8c72c6e2aee6e74efa467eefe5ca844d45 100644 --- a/6-InRealEnvironment/1-SetUpEnvironment.ipynb +++ b/6-InRealEnvironment/1-SetUpEnvironment.ipynb @@ -23,7 +23,7 @@ "source": [ "## Introduction \n", "\n", - "I will present here briefly how to set up a minimal development environment... only in Unix-like systems: sorry for Windows developers, but I have never set up a Windows environment for development.\n", + "I will present here briefly how to set up a minimal development environment... only in Unix-like systems: sorry for Windows developers, but I have never set up a Windows environment for development. You may have a look at WSL, which is gaining traction and enables you to use Linux inside Windows.\n", "\n", "This will explain installation for two mainstreams compilers: [GNU compiler for C++](https://en.wikipedia.org/wiki/GNU_Compiler_Collection) and [clang++](https://en.wikipedia.org/wiki/Clang).\n", "\n", @@ -48,7 +48,7 @@ "outputs": [], "source": [ "// In a terminal\n", - "sudo apt-get install -y clang++-11" + "sudo apt-get install -y clang++-13" ] }, { @@ -74,7 +74,7 @@ "source": [ "#### More recent gcc\n", "\n", - "However, Ubuntu is rather conservative and the version you get might be a bit dated and it might be problematic if you intend to use the bleeding-edged features from the latest C++ standard (even if it's now better than what it used to be: with Ubuntu 18.04 you get the decent g++-7.3 now and gcc-9.3 with 20.04). \n", + "However, Ubuntu is rather conservative and the version you get might be a bit dated and it might be problematic if you intend to use the bleeding-edged features from the latest C++ standard (even if it's now better than what it used to be).\n", "\n", "To get a more recent version, you need to use this [PPA](https://launchpad.net/ubuntu/+ppas):" ] @@ -95,8 +95,8 @@ "sudo add-apt-repository ppa:ubuntu-toolchain-r/test\n", "sudo apt-get update\n", "\n", - "// Installing the more recent gcc; which is 10 at the time of this writing (April 2021)\n", - "sudo apt-get install -y g++-10" + "// Installing the more recent gcc; which is 10 at the time of this writing (September 2022)\n", + "sudo apt-get install -y g++-12" ] }, { @@ -113,14 +113,14 @@ "outputs": [], "source": [ "// In a terminal\n", - "sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-10 100" + "sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 100" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "More realistically, you will install gcc and perhaps gfortran as well; the following command make sure all are kept consistent (you do not want to mesh gcc 7 with g++ 9 for instance...):" + "More realistically, you will install gcc and perhaps gfortran as well; the following command make sure all are kept consistent (you do not want to mesh gcc 9 with g++ 12 for instance...):" ] }, { @@ -130,9 +130,9 @@ "outputs": [], "source": [ "// In a terminal\n", - "sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 100 \n", - " --slave /usr/bin/g++ g++ /usr/bin/g++-10\n", - " --slave /usr/bin/gfortran gfortran /usr/bin/gfortran-10" + "sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 100 \n", + " --slave /usr/bin/g++ g++ /usr/bin/g++-12\n", + " --slave /usr/bin/gfortran gfortran /usr/bin/gfortran-12" ] }, { @@ -200,9 +200,9 @@ "\n", "Besides the compiler, you may also choose which implementation of the STL you want to use. There are two mainstream choices:\n", "\n", - "- `libstdc++`, which is the STL provided along gcc by GNU. This is the default choice for many Linux distro, and there is fat chance the binaries, libraries and share libraries in your package system was compiled with this one.\n", + "- `libstdc++`, which is the STL provided along gcc by GNU. This is the default choice for many Linux distro, and there is fat chance the binaries, libraries and share libraries in your package system were compiled with this one.\n", "\n", - "- `libc++`, which is the STL provided along clang by LLVM. It is the default choice on macOS, and was until recently a pain to use with Ubuntu (according to Laurent it is much better now in Ubuntu 20.04.\n", + "- `libc++`, which is the STL provided along clang by LLVM. It is the default choice on macOS, and was until recently a pain to use with Ubuntu (according to Laurent it is much better now in Ubuntu 20.04 and more recent versions).\n", "\n", "Both are pretty solid choices:\n", "\n", @@ -244,8 +244,8 @@ "* [Eclipse CDT](https://www.eclipse.org/cdt/) and [NetBeans](https://netbeans.org/) are other IDEs with more mileage.\n", "* [QtCreator](https://www.qt.io/qt-features-libraries-apis-tools-and-ide) is not just for Qt edition and might be used as a C++ IDE as well.\n", "* [XCode](https://developer.apple.com/xcode) is the editor provided by Apple on macOS.\n", - "* [KDevelop](https://www.kdevelop.org/) is the IDE from the KDE project. Combination of an advanced editor with semantic code analysis.\n", - "* [JupyterLab](https://jupyter.org/) this very same notebook lab can be used as IDE after the last improvements and extensions added, [see this](https://towardsdatascience.com/jupyter-is-now-a-full-fledged-ide-c99218d33095) and how include the [VS Code Monaco Editor](https://imfing.medium.com/bring-vs-code-to-your-jupyterlab-187e59dd1c1b).\n" + "* [KDevelop](https://www.kdevelop.org/) is the IDE from the KDE project.\n", + "* [JupyterLab](https://jupyter.org/) this very same notebook lab can be used as IDE after the last improvements and extensions added, [see this](https://towardsdatascience.com/jupyter-is-now-a-full-fledged-ide-c99218d33095) and how include the [VS Code Monaco Editor](https://imfing.medium.com/bring-vs-code-to-your-jupyterlab-187e59dd1c1b). I wouldn't advise it but if you're really keen to roll with it.\n" ] }, { @@ -260,7 +260,7 @@ "\n", "The most obvious choice for a SCM is [git](https://git-scm.com) which is now widely abroad and has become the _de facto_ standard. _git_ is extremely versatile but you can already do a lot of version control with around 10 commands so the learning curve is not as steep as you may fear.\n", "\n", - "git is generally already installed on your system or readily available through your package manager." + "git is generally already installed on your system or readily available through your package manager (or by installing XCode and its tools on macOS)." ] }, { @@ -279,7 +279,8 @@ "* [meson](https://mesonbuild.com/) is a more recent alternative which aims to be simpler to use than CMake. Never used it so can't say much about it.\n", "* [SCons](https://www.scons.org/) is a build system built upon Python which lets you write your own Python functions in the build system. The concept is appealing, but the actual use is actually dreadful and the provided build is much slower than what other build system provides. Avoid it!\n", "* [Ninja](https://ninja-build.org) is presented on this website as _a small build system with a focus on speed. It differs from other build systems in two major respects: it is designed to have its input files generated by a higher-level build system, and it is designed to run builds as fast as possible_. It is my favorite generator to use with CMake; meson also enables usage of Ninja under the hood.\n", - "\n" + "\n", + "We will illustrate in next notebook a basic use of CMake.\n" ] }, { diff --git a/6-InRealEnvironment/2-FileStructure.ipynb b/6-InRealEnvironment/2-FileStructure.ipynb index 0e24458c4276ab7dcc610399afa689fcc059284e..45dc4d5294a835e20a76007c5b4a03e095c2b257 100644 --- a/6-InRealEnvironment/2-FileStructure.ipynb +++ b/6-InRealEnvironment/2-FileStructure.ipynb @@ -593,7 +593,7 @@ "#ifndef HELLO_HPP\n", "#define HELLO_HPP\n", "\n", - "void hello();\n", + "void Hello();\n", "\n", "#endif // HELLO_HPP" ] @@ -664,7 +664,7 @@ "\n", "int main(int argc, char** argv)\n", "{\n", - " hello();\n", + " Hello();\n", " \n", " return EXIT_SUCCESS;\n", "}" diff --git a/6-InRealEnvironment/3-Compilers.ipynb b/6-InRealEnvironment/3-Compilers.ipynb index 9cdf838971ae8e8ab3c60bc08abbf648f82f33bb..4b5be5c25a7b5926f31cdec1932a2d85967df293 100644 --- a/6-InRealEnvironment/3-Compilers.ipynb +++ b/6-InRealEnvironment/3-Compilers.ipynb @@ -25,7 +25,7 @@ "\n", "I will present here briefly some characteristics of both gcc and clang compilers.\n", "\n", - "I recommend using them both (and more if you can!): each compiler gets its own spin on the standard, and sometimes a perfectly valid code will be refused by one... whereas (more often) invalid code will unduly get a free pass with one of the compiler.\n", + "I recommend using them both (and more if you can!): each compiler gets its own spin on the standard, and sometimes a perfectly valid code will be refused by one... whereas (more often) invalid code will unduly get a free pass with one of the compilers.\n", "\n", "So the more compiler with which you may test your code, the merrier!\n", "\n", @@ -36,7 +36,7 @@ "\n", "[GCC](http://gcc.gnu.org/) is a free-to-use compiler which has now been around for decades; it is mostly for Unix systems but may be used with Windows with some additional set-up (I don't master this but see for instance this [StackOverflow question](https://stackoverflow.com/questions/771756/what-is-the-difference-between-cygwin-and-mingw)).\n", "\n", - "As many others softwares, GCC changed its version system: gcc 3 and 4 were there for decades, and now the versions change more swiftly, with gcc 11 the current stable version (published on the 27th of April 2021).\n", + "As many others softwares, GCC changed its version system: gcc 3 and 4 were there for decades, and now the versions change more swiftly, with gcc 12.2 the current stable version (as of September 2022; it was published on the 19th of August 2022).\n", "\n", "`gcc` was long known for its terse user interface: until recently color were not provided in outputs, and error messages were a bit cryptic for the beotians. It changed though when `clang` appeared.\n", "\n", @@ -65,7 +65,7 @@ "* `-DNDEBUG` means the macro `NDEBUG` is defined; this deactivates all asserts in the code.\n", "* `-O3` means as many optimizations as possible should be applied.\n", "\n", - "You may sometimes find on the Web advocates of `-O2` flag, which performs slightly less optimization than `-O3`, on the ground that `-O3` breaks some code. It was true many years ago... but now if your code breaks under `-O3` it probably means it's buggy, not that optimization is! You may read [this thread](https://stackoverflow.com/questions/11546075/is-optimisation-level-o3-dangerous-in-g) for more about the question; I raised this point because you might be surprised by the number of libraries which use up O2 in their release mode.\n", + "You may sometimes find on the Web advocates of `-O2` flag, which performs slightly less optimization than `-O3`, on the ground that `-O3` breaks some code. It was true many years ago... but now if your code breaks under `-O3` it probably means it's buggy, not that optimization is! You may read [this thread](https://stackoverflow.com/questions/11546075/is-optimisation-level-o3-dangerous-in-g) for more about the question; I raised this point because you might be surprised by the number of libraries which still use up O2 in their release mode.\n", "\n", "\n", "### Warnings\n", @@ -130,7 +130,7 @@ "-Wno-global-constructors // same\n", "-Wno-documentation // Some Doxygen were incorrectly indicated as inadequate\n", "-Wno-documentation-unknown-command // A valid Doxygen command I used was not recognized\n", - "-Wno-undefined-func-template // Required weird code in header file; didn't understand this one to be honest\n", + "-Wno-undefined-func-template // Requires weird code in header file; didn't understand this one to be honest\n", "-Wno-c++1z-extensions // I assume my code uses up C++ 17\n", "-Wno-unused-template // A very recent one that tells if a template is never used \n", "````\n", @@ -151,7 +151,9 @@ "\n", "### Fortran support\n", "\n", - "There is no Fortran compiler with LLVM or clang; you should therefore use gfortran if a third-party library you use require it. Usually take the most recent you may find.\n", + "For a long time, there was no Fortran compiler with LLVM or clang; you therefore had to use something as gfortran if a third-party library you use require it - usually the most recent you may find.\n", + "\n", + "As of 2022, there is something called Flang that exists, but my attempt to use it didn't go far as they chose contrary to clang did years ago to use their own options without pseudo backward compatibility with gfortran's ones. As a result, compilation of third party libraries is tricky as they often assume interface provided by gfortran (Openblas and PETSc for instance won't compile with Flang). So for the time being the best is probably to stick with gfortran if as myself you aren't a Fortran developer but may need it for your third party dependencies.\n", "\n", "### Apple Clang\n", "\n", @@ -229,5 +231,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/6-InRealEnvironment/4-ThirdParty.ipynb b/6-InRealEnvironment/4-ThirdParty.ipynb index 7075dee59214fa61c8ea813e442a37987e357011..329e41427e8edb7bf9dc5f3518fc7e596227d0c8 100644 --- a/6-InRealEnvironment/4-ThirdParty.ipynb +++ b/6-InRealEnvironment/4-ThirdParty.ipynb @@ -31,7 +31,7 @@ "\n", "* Fixing the warnings from the third-party library yourself is a dead-end: you would have to redo-it at each upgrade of the library... and doing it in the first place may not be that easy with a base code you do not know. \n", "* Of course, you may ask the library developers to fix it, or even better provide a **pull request** to do it for them... but they may not be keen to accept it, and argue they know what they're doing and that you should shut the damn compiler warning up. But you really shouldn't: the warnings you want to use or not should be dictated by your own needs, not by the third-party libraries.\n", - "* Keeping the warning is also bad: as already mentioned if you're letting even 10 warnings in your code you may not see the dangerous 11th a change in the code may produce.\n", + "* Keeping the warnings is also bad: as already mentioned if you're letting even 10 warnings in your code you may not see the dangerous 11th a change in the code may produce.\n", "\n", "## Demo\n", "\n", diff --git a/6-InRealEnvironment/5-Namespace.ipynb b/6-InRealEnvironment/5-Namespace.ipynb index ed3c4149416cc2d210226405d68c0a3005ca3c5d..312ea0db2125e65eaa6d5aae20a3799f082f0a55 100644 --- a/6-InRealEnvironment/5-Namespace.ipynb +++ b/6-InRealEnvironment/5-Namespace.ipynb @@ -188,6 +188,8 @@ "metadata": {}, "outputs": [], "source": [ + "// hpp file\n", + "\n", "namespace MyProject\n", "{\n", " namespace Internal\n", @@ -204,6 +206,8 @@ "metadata": {}, "outputs": [], "source": [ + "// cpp file\n", + "\n", "namespace MyProject\n", "{\n", " namespace Internal\n", diff --git a/6-InRealEnvironment/6-Tools.ipynb b/6-InRealEnvironment/6-Tools.ipynb index ca3280efcd7d394b2e4431f839091bd2ae117fc4..4602377e59291e2c7a2b7775ede376bcbb83ecdf 100644 --- a/6-InRealEnvironment/6-Tools.ipynb +++ b/6-InRealEnvironment/6-Tools.ipynb @@ -73,7 +73,7 @@ "\n", "### Doctest\n", "\n", - "[doctest](https://github.com/onqtam/doctest) is a C++ testing framework but is by far the fastest both in compile times (by orders of magnitude) and runtime compared to other feature-rich alternatives. It brings the ability of compiled languages to have tests written directly in the production code thanks to a fast, transparent and flexible test runner with a clean interface.\n", + "[doctest](https://github.com/onqtam/doctest) is another C++ testing framework that was very appreciated by one of the teacher (Vicente) in the 2021 session of this tutorial.\n", "\n", "\n", "### BoostTest\n", @@ -97,7 +97,7 @@ "\n", "I can mention [Uncrustify](http://uncrustify.sourceforge.net/): plenty of options to configure, even if they're not easy to figure out.\n", "\n", - "[clang-format](https://clang.llvm.org/docs/ClangFormat.html) probably provides a better trade-off power/complexity but requires LLVM clang to be installed on your system (AppleClang won't do). Can be easily installed through Homebrew though.\n", + "[clang-format](https://clang.llvm.org/docs/ClangFormat.html) probably provides a better trade-off power/complexity but requires LLVM clang to be installed on your system (AppleClang won't do, but you can easily install it on macOS with Homebrew). Can be easily installed through Homebrew though.\n", "\n", "\n", "## Doxygen\n", @@ -106,10 +106,10 @@ "\n", "It is rather easy to set up to easy task but may become a tad more difficult once you want to tackle more advanced features. Nonetheless it is the de-facto standard for C++ documentation and it's really something you should set up quite early if you're working on a project meant to stay for a while: it's really a hassle to spend countless hours providing after the fact such guidance in the code. As for compilers, you should strive to provide a documentation without any warning.\n", "\n", - "An important drawback of Doxygen is that its \"compilation\" of your project is much less advanced than the one provided by your compiler:\n", + "An important drawback of Doxygen is that its \"compilation\" of your project is much less advanced than the one provided by your compiler (even if they clearly upped their game in recent years):\n", "\n", "* No parallelism in some steps.\n", - "* Everything is redone at each Doxygen call.\n", + "* A lot is done again at each Doxygen call.\n", "\n", "So expect for important projects the time to generate Doxygen documentation to be bigger than compilation time. \n", "\n", diff --git a/7-Appendix/0-main.ipynb b/7-Appendix/0-main.ipynb index 082a1760b33a7ff1e94799b8789406f9289f2f17..3aed15987870ec2ad14e1e6c615433c7caa9cc18 100644 --- a/7-Appendix/0-main.ipynb +++ b/7-Appendix/0-main.ipynb @@ -13,10 +13,10 @@ "source": [ "This appendix groups some stuff just mentioned in earlier tutorials but that I have chosen not to speak longly about because the lecture is already long enough as it is and they might be a tiny bit more complex for some of them.\n", "\n", - "* [Breaking circular definition of shared pointers with weak pointers](./WeakPtr.ipynb) explains how to set up properly a circular relationship shared/weak pointers (unfortunately the way to set it up properly is often coyly mentioned but not explained); this is a follow-up of the notebook about [smart pointers](../5-UsefulConceptsAndSTL/6-SmartPointers.ipynb).\n", - "\n", "* [CRTP](./Crtp.ipynb) is one of my favourite idiom to provide a same functionality to utterly different classes; it was teased in the [notebook](../4-Templates/5-MoreAdvanced.ipynb) about advanced features with templates.\n", "\n", + "* [Breaking circular definition of shared pointers with weak pointers](./WeakPtr.ipynb) explains how to set up properly a circular relationship shared/weak pointers (unfortunately the way to set it up properly is often coyly mentioned but not explained); this is a follow-up of the notebook about [smart pointers](../5-UsefulConceptsAndSTL/6-SmartPointers.ipynb). An existing CRTP from STL is used there at some point so you should probably consider reading it after the one explaining CRTP, but it is by no means a hard prerequisite.\n", + "\n", "* [Homemade exceptions](./HomemadeException.ipynb) just provides the instantiation of the class I personally use when I want to raise an exception; it's a direct follow-up of the section that [mentioned it](../5-UsefulConceptsAndSTL/1-ErrorHandling.ipynb#The-exception-class-I-use).\n", "\n", "* [Switch](./Switch.ipynb) is the less important one: it just explains `switch` statement and the syntax caveats you might encounter with them. It was mentioned in the [early notebook](../1-ProceduralProgramming/2-Conditions-and-loops.ipynb#switch-statement) about conditions.\n", diff --git a/7-Appendix/HomemadeException.ipynb b/7-Appendix/HomemadeException.ipynb index 0509f126deab0acea484be9f7a51f5ba574c7cb4..a5ddc92d352c1faff645771d3ff134c7a673f3d0 100644 --- a/7-Appendix/HomemadeException.ipynb +++ b/7-Appendix/HomemadeException.ipynb @@ -93,7 +93,7 @@ "\n", "### Indicating the file and line\n", "\n", - "If your code is huge, knowing the exception itself is not enough, if this exception may be thrown from several locations in your code. To identify where the exception was thrown, I use the `__FILE__` and `__LINE__` macro which gives the file and line where they were found in the code (better alternative [may appear](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4562.html#reflection.src_loc) in C++ 20).\n", + "If your code is huge, knowing the exception itself is not enough, if this exception may be thrown from several locations in your code. To identify where the exception was thrown, I use the `__FILE__` and `__LINE__` macro which gives the file and line where they were found in the code (better alternative `std::source_location` is present in C++ 20 but is unfortunately [not well supported yet](https://en.cppreference.com/w/cpp/compiler_support)).\n", "\n", "So my constructor looks like:\n", "\n", @@ -348,5 +348,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/7-Appendix/StringView.ipynb b/7-Appendix/StringView.ipynb index d5cc5865c7dc61a8df7aa76eb1f30b0eeb2da5cc..1e0887b9c6747b9f0ce27ab512732d45e0fed247 100644 --- a/7-Appendix/StringView.ipynb +++ b/7-Appendix/StringView.ipynb @@ -29,7 +29,7 @@ "#include <string_view>\n", "#include <iostream>\n", "\n", - "void print(std::string_view content)\n", + "void Print(std::string_view content)\n", "{\n", " std::cout << \"Content is '\" << content << \"'\" << std::endl;\n", "}\n", @@ -37,9 +37,9 @@ "\n", "std::string hello(\"Hello world from std::string!\");\n", "\n", - "print(hello);\n", + "Print(hello);\n", "\n", - "print(\"Hello world!\");" + "Print(\"Hello world!\");" ] }, { @@ -60,7 +60,7 @@ "#include <string>\n", "#include <iostream>\n", "\n", - "void print_with_const_ref(const std::string& content)\n", + "void PrintWithConstRef(const std::string& content)\n", "{\n", " std::cout << \"Content is '\" << content << \"'\" << std::endl;\n", "}\n", @@ -68,8 +68,8 @@ "\n", "std::string hello(\"Hello world from std::string!\");\n", "\n", - "print_with_const_ref(hello);\n", - "print_with_const_ref(\"Hello world!\");" + "PrintWithConstRef(hello);\n", + "PrintWithConstRef(\"Hello world!\");" ] }, { @@ -100,12 +100,12 @@ "#include <string>\n", "#include <iostream>\n", "\n", - "void print_with_ref(std::string& content)\n", + "void PrintWithRef(std::string& content)\n", "{\n", " std::cout << \"Content is '\" << content << \"'\" << std::endl;\n", "}\n", "\n", - "print_with_ref(\"Hello world!\"); // COMPILATION ERROR!" + "PrintWithRef(\"Hello world!\"); // COMPILATION ERROR!" ] }, { @@ -159,7 +159,7 @@ "metadata": {}, "source": [ "\n", - "© _Inria 2021-2022_ \n", + "© _Inria 2021_ \n", "_This notebook is released 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/)_ " ] } diff --git a/7-Appendix/Switch.ipynb b/7-Appendix/Switch.ipynb index 4b550a69ff07aacd8b6a55b69bdfe47a30d2b5fa..db7f3ee09a32b1b02f15699f7052b4ba17a5e51f 100644 --- a/7-Appendix/Switch.ipynb +++ b/7-Appendix/Switch.ipynb @@ -302,5 +302,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/7-Appendix/WeakPtr.ipynb b/7-Appendix/WeakPtr.ipynb index 99eea7d8f44de69abac9a2aeeb5a21934177fdd9..7056799957d9d64b957d47ff2e919b48cf5acef4 100644 --- a/7-Appendix/WeakPtr.ipynb +++ b/7-Appendix/WeakPtr.ipynb @@ -225,7 +225,7 @@ "\n", "It is only used for storage; you can't use the object underneath directly and have to build a `shared_ptr` from it before use with the method `lock()`. \n", "\n", - "There are just two modifications to do (used to be done here but circa 2021 Xeus emit a link so the code will be put instead [@Coliru](http://coliru.stacked-crooked.com/a/2013674aee9983d5):" + "There are just two modifications to do (used to be done here but circa 2021 Xeus emit a link so the code will be put instead [@Coliru](http://coliru.stacked-crooked.com/a/2013674aee9983d5)):" ] }, { @@ -314,7 +314,7 @@ "source": [ "This code causes an issue (which might be a crash or an error depending on your platform): the vector still includes `Yoda` as a child of `Darth Vader`, whereas the object no longer exists (we're once again in undefined behavior territory).\n", "\n", - "There is a method `expired()` that returns `true` if the underlying object no longer exists; but in fact lock gets a return value that may be used to the same purpose (see [@Coliru](http://coliru.stacked-crooked.com/a/fcdb5cf21897e4ef)):" + "There is a method `expired()` that returns `true` if the underlying object no longer exists; but in fact `lock()` gets a return value that may be used to the same purpose (see [@Coliru](http://coliru.stacked-crooked.com/a/fcdb5cf21897e4ef)):" ] }, { @@ -741,5 +741,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 }