From b12e03e82db40555eb8acccc2f8c0bf453b01b7f Mon Sep 17 00:00:00 2001 From: Changkun Ou Date: Sat, 20 Jul 2019 13:34:18 +0200 Subject: [PATCH] book: translate ch02 Update #12 --- book/en-us/02-usability.md | 1109 ++++++++++++++++- book/zh-cn/02-usability.md | 63 +- .../2.7.cpp => 2.19.delegate.constructor.cpp} | 20 +- .../2.8.cpp => 2.20.strong.type.enum.cpp} | 9 +- 4 files changed, 1162 insertions(+), 39 deletions(-) rename code/2/{todo/2.7.cpp => 2.19.delegate.constructor.cpp} (75%) rename code/2/{todo/2.8.cpp => 2.20.strong.type.enum.cpp} (85%) diff --git a/book/en-us/02-usability.md b/book/en-us/02-usability.md index 1d821e49..9ff5b845 100644 --- a/book/en-us/02-usability.md +++ b/book/en-us/02-usability.md @@ -6,9 +6,1114 @@ order: 2 # Chapter 02: Language Usability Enhancements -[Table of Content](./toc.md) | [Previous Chapter](./01-intro.md) | [Next Chapter: Language Runtime Enhancements](./03-runtime.md) +[TOC] + +When we declare, define a variable or constant, and control the flow of code, +object-oriented functions, template programming, etc., before the runtime, +it may happen when writing code or compiler compiling code. +To this end, we usually talk about **language usability**, +which refers to the language behavior that occurred before the runtime. + +## 2.1 Constants + +### nullptr + +The purpose of `nullptr` appears to replace `NULL`. In a sense, +traditional C++ treats `NULL` and `0` as the same thing, +depending on how the compiler defines NULL, +and some compilers define NULL as `((void*)0)` Some will define it directly as `0`. + +C++ ** does not allow ** to implicitly convert `void *` to other types. +But if the compiler tries to define `NULL` as `((void*)0)`, then in the following code: + +```cpp +char *ch = NULL; +``` + +C++ without the `void *` implicit conversion has to define `NULL` as `0`. +This still creates a new problem. Defining `NULL` to 0 will cause the overloading feature in `C++` to be confusing. +Consider the following two `foo` functions: + +```cpp +void foo(char*); +void foo(int); +``` + +Then the `foo(NULL);` statement will call `foo(int)`, which will cause the code to be counterintuitive. + +To solve this problem, C++11 introduced the `nullptr` keyword, which is specifically used to distinguish null pointers, 0. The type of `nullptr` is `nullptr_t`, which can be implicitly converted to any pointer or member pointer type, and can be compared equally or unequally with them. + +You can try to compile the following code using clang++: + +```cpp +#include +#include + +void foo(char *); +void foo(int); + +int main() { + if (std::is_same::value) + std::cout << "NULL == 0" << std::endl; + if (std::is_same::value) + std::cout << "NULL == (void *)0" << std::endl; + if (std::is_same::value) + std::cout << "NULL == nullptr" << std::endl; + + foo(0); // will call foo(int) + // foo(NULL); // doen't compile + foo(nullptr); // will call foo(char*) + return 0; +} + +void foo(char *) { + std::cout << "foo(char*) is called" << std::endl; +} +void foo(int i) { + std::cout << "foo(int) is called" << std::endl; +} +``` + +The outputs are: + +```bash +foo(int) is called +foo(char*) is called +``` + +From the output we can see that `NULL` is different from `0` and `nullptr`. +So, develop the habit of using `nullptr` directly. + +In addition, in the above code, we used `decltype` and +`std::is_same` which are modern C++ syntax. +In simple terms, `decltype` is used for type derivation, +and `std::is_same` is used. +To compare the equality of the two types, +we will discuss them in detail later in the [decltype](#decltype) section. + +### constexpr + +C++ itself already has the concept of constant expressions, +such as 1+2, 3*4. Such expressions always produce the same result +without any side effects. If the compiler can directly optimize +and embed these expressions into the program at compile time, +it will increase the performance of the program. +A very obvious example is in the definition phase of an array: + +```cpp +#include +#define LEN 10 + +int len_foo() { + int i = 2; + return i; +} +constexpr int len_foo_constexpr() { + return 5; +} + +constexpr int fibonacci(const int n) { + return n == 1 || n == 2 ? 1 : fibonacci(n-1) + fibonacci(n-2); +} + + +int main() { + char arr_1[10]; // legal + char arr_2[LEN]; // legal + + int len = 10; + // char arr_3[len]; // illegal + + const int len_2 = len + 1; + constexpr int len_2_constexpr = 1 + 2 + 3; + // char arr_4[len_2]; // illegal, but ok for most of the compilers + char arr_4[len_2_constexpr]; // legal + + // char arr_5[len_foo()+5]; // illegal + char arr_6[len_foo_constexpr() + 1]; // legal + + // 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 + std::cout << fibonacci(10) << std::endl; + + return 0; +} +``` + +In the above example, `char arr_4[len_2]` may be confusing because `len_2` has been defined as a constant. +Why is `char arr_4[len_2]` still illegal? +This is because the length of the array in the C++ standard must be a constant expression, +and for `len_2`, this is a `const` constant, not a constant expression, +so even if this behavior is in most compilers Both support, but) it is an illegal behavior, +we need to use the `constexpr` feature introduced in C++11, which will be introduced next, +to solve this problem; for `arr_5`, before C++98 The compiler cannot know that `len_foo()` +actually returns a constant at runtime, which causes illegal production. + +> Note that most compilers now have their own compiler optimizations. +> Many illegal behaviors become legal under the compiler's optimization. +> If you need to reproduce the error, you need to use the old version of the compiler. + +C++11 provides `constexpr` to let the user explicitly declare that the function or +object constructor will become a constant expression at compile time. +This keyword explicitly tells the compiler that it should verify that `len_foo` +should be a compile time. Constant expression. + +In addition, the function of `constexpr` can use recursion: + +```cpp +constexpr int fibonacci(const int n) { + return n == 1 || n == 2 ? 1 : fibonacci(n-1) + fibonacci(n-2); +} +``` + +Starting with C++14, +the constexpr function can use simple statements such as local variables, +loops, and branches internally. +For example, the following code cannot be compiled under the C++11 standard: + +```cpp +constexpr int fibonacci(const int n) { + if(n == 1) return 1; + if(n == 2) return 1; + return fibonacci(n-1) + fibonacci(n-2); +} +``` + +To do this, we can write a simplified version like this +to make the function available from C++11: + +```cpp +constexpr int fibonacci(const int n) { + return n == 1 || n == 2 ? 1 : fibonacci(n-1) + fibonacci(n-2); +} +``` + +## 2.2 Variables and initialization + +### if-switch + +In traditional C++, the declaration of a variable can declare a temporary variable `int` +even though it can be located anywhere, even within a `for` statement, +but there is always no way to declare a temporary variable in the `if` and `switch` statements. +E.g: + +```cpp +#include +#include +#include + +int main() { + std::vector vec = {1, 2, 3, 4}; + + // after c++17, can be simplefied by using `auto` + const std::vector::iterator itr = std::find(vec.begin(), vec.end(), 2); + if (itr != vec.end()) { + *itr = 3; + } + + if (const std::vector::iterator itr = std::find(vec.begin(), vec.end(), 3); + itr != vec.end()) { + *itr = 4; + } + + // should output: 1, 4, 3, 4. can be simplefied using `auto` + for (std::vector::iterator element = vec.begin(); element != vec.end(); ++element) + std::cout << *element << std::endl; +} +``` + +In the above code, we can see that the `itr` variable is defined in the scope of +the entire `main()`, which causes us to rename the other when we need to traverse +the entire `std::vectors` again. A variable. C++17 eliminates this limitation so that +we can do this in if(or switch): + +```cpp +if (const std::vector::iterator itr = std::find(vec.begin(), vec.end(), 3); + itr != vec.end()) { + *itr = 4; +} +``` + +Is it similar to the Go? + +### Initializer list + +Initialization is a very important language feature, +the most common one is when the object is initialized. +In traditional C++, different objects have different initialization methods, +such as ordinary arrays, PODs (**P**lain **O**ld **D**ata, +ie classes without constructs, destructors, and virtual functions) +Or struct type can be initialized with `{}`, +which is what we call the initialization list. +For the initialization of the class object, +you need to use the copy construct, +or you need to use `()`. +These different methods are specific to each other and cannot be generic. +E.g: + +```cpp +#include +#include + +class Foo { +public: + int value_a; + int value_b; + Foo(int a, int b) : value_a(a), value_b(b) {} +}; + +int main() { + // before C++11 + int arr[3] = {1, 2, 3}; + Foo foo(1, 2); + std::vector vec = {1, 2, 3, 4, 5}; + + std::cout << "arr[0]: " << arr[0] << std::endl; + std::cout << "foo:" << foo.value_a << ", " << foo.value_b << std::endl; + for (std::vector::iterator it = vec.begin(); it != vec.end(); ++it) { + std::cout << *it << std::endl; + } + return 0; +} +``` + +To solve this problem, +C++11 first binds the concept of the initialization list to the type +and calls it `std::initializer_list`, +allowing the constructor or other function to use the initialization list +like a parameter, which is The initialization of class objects provides +a unified bridge between normal arrays and POD initialization methods, +such as: + +```cpp +#include +class MagicFoo { +public: + std::vector vec; + MagicFoo(std::initializer_list list) { + for (std::initializer_list::iterator it = list.begin(); + it != list.end(); ++it) + vec.push_back(*it); + } +}; +int main() { + // after C++11 + MagicFoo magicFoo = {1, 2, 3, 4, 5}; + + std::cout << "magicFoo: "; + for (std::vector::iterator it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it) std::cout << *it << std::endl; +} +``` + +This constructor is called the initialize list constructor, and the type with +this constructor will be specially taken care of during initialization. + +In addition to the object construction, the initialization list can also +be used as a formal parameter of a normal function, for example: + +```Cpp +public: + void foo(std::initializer_list list) { + for (std::initializer_list::iterator it = list.begin(); it != list.end(); ++it) vec.push_back(*it); + } + +magicFoo.foo({6,7,8,9}); +``` + +Second, C++11 also provides a uniform syntax for initializing arbitrary objects, such as: + +```cpp +Foo foo2 {3, 4}; +``` + +### Structured binding + +Structured bindings provide functionality similar to the multiple return values +provided in other languages. In the chapter on containers, +we will learn that C++11 has added a `std::tuple` container for +constructing a tuple that encloses multiple return values. But the flaw +is that C++11/14 does not provide a simple way to get and define +the elements in the tuple directly from the tuple, +although we can unpack the tuple using `std::tie` +But we still have to be very clear about how many objects this tuple contains, +what type of each object is, very troublesome. + +C++17 completes this setting, +and the structured bindings let us write code like this: + +```cpp +#include + +std::tuple f() { + return std::make_tuple(1, 2.3, "456"); +} + +int main() { + auto [x, y, z] = f(); + std::cout << x << ", " << y << ", " << z << std::endl; + return 0; +} +``` + +The `auto` type derivation is described in the +[auto type inference](#auto) section. + +## 2.3 Type inference + +In traditional C and C++, the types of parameters must be clearly defined, which does not help us to quickly encode, especially when we are faced with a large number of complex template types, we must clearly indicate the type of variables in order to proceed. Subsequent coding, which not only slows down our development efficiency, but also makes the code stinking and long. + +C++11 introduces the two keywords `auto` and `decltype` to implement type derivation, letting the compiler worry about the type of the variable. This makes C++ the same as other modern programming languages, in a way that provides the habit of not having to worry about variable types. + +### auto + +`auto` has been in C++ for a long time, but it always exists as an indicator of a storage type, coexisting with `register`. In traditional C++, if a variable is not declared as a `register` variable, it is automatically treated as an `auto` variable. And with `register` being deprecated (used as a reserved keyword in C++17 and later used, it doesn't currently make sense), the semantic change to `auto` is very natural. + +One of the most common and notable examples of type derivation using `auto` is the iterator. You should see the lengthy iterative writing in traditional C++ in the previous section: + +```cpp +// before C++11 +// cbegin() returns vector::const_iterator +// and therefore itr is type vector::const_iterator +for(vector::const_iterator it = vec.cbegin(); itr != vec.cend(); ++it) +``` + +When we have `auto`: + +```cpp +#include +#include +#include + +class MagicFoo { +public: + std::vector vec; + MagicFoo(std::initializer_list list) { + for (auto it = list.begin(); it != list.end(); ++it) { + vec.push_back(*it); + } + } +}; + +int main() { + MagicFoo magicFoo = {1, 2, 3, 4, 5}; + std::cout << "magicFoo: "; + for (auto it = magicFoo.vec.begin(); it != magicFoo.vec.end(); ++it) { + std::cout << *it << ", "; + } + std::cout << std::endl; + return 0; +} +``` + +Some other common usages: + +```cpp +auto i = 5; // i as int +auto arr = new auto(10); // arr as int * +``` + +> **Note**: `auto` cannot be used for function arguments, so the following +> is not possible to compile (considering overloading, +> we should use templates): +> ```cpp +> int add(auto x, auto y); +> +> 2.6.auto.cpp:16:9: error: 'auto' not allowed in function prototype +> int add(auto x, auto y) { +> ^~~~ +> ``` +> +> In addition, `auto` cannot be used to derive array types: +> +> ```cpp +> auto auto_arr2[10] = arr; // illegal, can't infer array type +> +> 2.6.auto.cpp:30:19: error: 'auto_arr2' declared as array of 'auto' +> auto auto_arr2[10] = arr; +> ``` + +### decltype + +The `decltype` keyword is used to solve the defect that the auto keyword +can only type the variable. Its usage is very similar to `sizeof`: + + +```cpp +decltype(expression) +``` + +Sometimes we may need to calculate the type of an expression, for example: + +```cpp +auto x = 1; +auto y = 2; +decltype(x+y) z; +``` + +You have seen in the previous example that +`decltype` is used to infer the usage of the type. +The following example is to determine +if the above variables `x, y, z` are of the same type: + +```cpp +if (std::is_same::value) + std::cout << "type x == int" << std::endl; +if (std::is_same::value) + std::cout << "type x == float" << std::endl; +if (std::is_same::value) + std::cout << "type z == type x" << std::endl; +``` + +Among them, `std::is_same` is used to determine whether +the two types `T` and `U` are equal. The output is: + +``` +type x == int +type z == type x +``` + +### tail type inference + +You may think that when we introduce `auto`, we have already mentioned that `auto` cannot be used for function arguments for type derivation. Can `auto` be used to derive the return type of a function? Still consider an example of an add function, which we have to write in traditional C++: + +```cpp +template +R add(T x, U y) { + return x+y +} +``` + +> Note: There is no difference between typename and class in the template parameter list. Before the keyword typename appears, class is used to define the template parameters. However, when defining a variable with [nested dependency type](http://en.cppreference.com/w/cpp/language/dependent_name#The_typename_disambiguator_for_dependent_names) in the template, you need to use typename to eliminate ambiguity. + +Such code is actually very ugly, because the programmer must explicitly +indicate the return type when using this template function. +But in fact we don't know what kind of operation +the `add()` function will do, and what kind of return type to get. + +This problem was solved in C++11. Although you may immediately +react to using `decltype` to derive the type of `x+y`, +write something like this: + +```cpp +decltype(x+y) add(T x, U y) +``` + +But in fact, this way of writing can not be compiled. +This is because `x` and `y` have not been defined +when the compiler reads decltype(x+y). +To solve this problem, C++11 also introduces a trailing return type, +which uses the auto keyword to post the return type: + +```cpp +template +auto add2(T x, U y) -> decltype(x+y){ + return x + y; +} +``` + +The good news is that from C++14 it is possible to directly derive the return value of +a normal function, so the following way becomes legal: + +```cpp +template +auto add3(T x, U y){ + return x + y; +} +``` + +You can check if the type derivation is correct: + +```cpp +// after c++11 +auto w = add2(1, 2.0); +if (std::is_same::value) { + std::cout << "w is double: "; +} +std::cout << w << std::endl; + +// after c++14 +auto q = add3(1.0, 2); +std::cout << "q: " << q << std::endl; +``` + +### decltype(auto) + +`decltype(auto)` is a slightly more complicated use of C++14. + +> To understand it you need to know the concept of parameter forwarding +> in C++, which we will cover in detail in the +> [Language Runtime Hardening](./03-runtime.md) chapter, +> and you can come back to the contents of this section later. + +In simple terms, `decltype(auto)` is mainly used to derive +the return type of a forwarding function or package, +which does not require us to explicitly specify +the parameter expression of `decltype`. +Consider the following example, when we need to wrap the following +two functions: + +```cpp +std::string lookup1(); +std::string& lookup2(); +``` + +In C++11: + +```cpp +std::string look_up_a_string_1() { + return lookup1(); +} +std::string& look_up_a_string_2() { + return lookup2(); +} +``` + +With `decltype(auto)`, we can let the compiler do this annoying parameter forwarding: + +```cpp +decltype(auto) look_up_a_string_1() { + return lookup1(); +} +decltype(auto) look_up_a_string_2() { + return lookup2(); +} +``` + +## 2.4 Control flow + +### if constexpr + +As we saw at the beginning of this chapter, we know that C++11 introduces the `constexpr` keyword, which compiles expressions or functions into constant results. A natural idea is that if we introduce this feature into the conditional judgment, let the code complete the branch judgment at compile time, can it make the program more efficient? C++17 introduces the `constexpr` keyword into the `if` statement, allowing you to declare the condition of a constant expression in your code. Consider the following code: -## Further Readings +```cpp +#include + +template +auto print_type_info(const T& t) { + if constexpr (std::is_integral::value) { + return t + 1; + } else { + return t + 0.001; + } +} +int main() { + std::cout << print_type_info(5) << std::endl; + std::cout << print_type_info(3.14) << std::endl; +} +``` + +At compile time, the actual code will behave as follows: + +```cpp +int print_type_info(const int& t) { + return t + 1; +} +double print_type_info(const double& t) { + return t + 0.001; +} +int main() { + std::cout << print_type_info(5) << std::endl; + std::cout << print_type_info(3.14) << std::endl; +} +``` + +### Range-based for loop + +Finally, C++11 introduces a range-based iterative method, and we have the ability to write loops that are as concise +as Python, and we can further simplify the previous example: + +```cpp +#include +#include +#include + +int main() { + std::vector vec = {1, 2, 3, 4}; + if (auto itr = std::find(vec.begin(), vec.end(), 3); itr != vec.end()) *itr = 4; + for (auto element : vec) + std::cout << element << std::endl; // read only + for (auto &element : vec) { + element += 1; // writeable + } + for (auto element : vec) + std::cout << element << std::endl; // read only +} +``` + +## 2.5 Templates + +C++ templates have always been a special art of the language, and templates can even be used independently as a new language. The philosophy of the template is to throw all the problems that can be processed at compile time into the compile time, and only deal with those core dynamic services at runtime, so as to greatly optimize the performance of the runtime. Therefore, templates are also regarded by many as one of the black magic of C++. + +### Extern templates + +In traditional C++, templates are instantiated by the compiler only when they are used. In other words, as long as a fully defined template is encountered in the code compiled in each compilation unit (file), it will be instantiated. This results in an increase in compile time due to repeated instantiations. Also, we have no way to tell the compiler not to trigger the instantiation of the template. + +To this end, C++11 introduces an external template that extends the syntax of the original mandatory compiler to instantiate a template at a specific location, allowing us to explicitly tell the compiler when to instantiate the template: + +```cpp +template class std::vector; // force instantiation +extern template class std::vector; // should not instantiation in current file +``` + +### The ">" + +In the traditional C++ compiler, `>>` is always treated as a right shift operator. But actually we can easily write the code for the nested template: + +```cpp +std::vector> matrix; +``` + +This is not compiled under the traditional C++ compiler, +and C++11 starts with continuous right angle brackets that become legal +and can be compiled successfully. +Even the following writing can be compiled by: + +```cpp +template +class MagicType { + bool magic = T; +}; + +// in main function: +std::vector2)>> magic; // legal, but not recommended +``` + +### Type alias templates + +Before you understand the type alias template, you need to understand the difference between "template" and "type". Carefully understand this sentence: **Templates are used to generate types.** In traditional C++, `typedef` can define a new name for the type, but there is no way to define a new name for the template. Because the template is not a type. E.g: + +```cpp +template +class MagicType { +public: + T dark; + U magic; +}; + +// not allowed +template +typedef MagicType, std::string> FakeDarkMagic; +``` + +C++11 uses `using` to introduce the following form of writing, and at the same time supports the same effect as the traditional `typedef`: + +> Usually we use `typedef` to define the alias syntax: `typedef original name new name; `, but the definition syntax for aliases such as function pointers is different, which usually causes a certain degree of difficulty for direct reading. + +```cpp +typedef int (*process)(void *); +using NewProcess = int(*)(void *); +template +using TrueDarkMagic = MagicType, std::string>; + +int main() { + TrueDarkMagic you; +} +``` + +### Default template parameters + +We may have defined an addition function: + +```cpp +template +auto add(T x, U y) -> decltype(x+y) { + return x+y; +} +``` + +However, when used, it is found that to use add, you must specify the type of its template parameters each time. + +A convenience is provided in C++11 to specify the default parameters of the template: + +```cpp +template +auto add(T x, U y) -> decltype(x+y) { + return x+y; +} +``` + +### Variadic templates + +The template has always been one of C++'s unique **Black Magic**. +In traditional C++, +both a class template and a function template could only accept +a fixed set of template parameters as specified; +C++11 added a new representation, allowing any number, +template parameters of any category, +and there is no need to fix the number of parameters when defining. + +```cpp +template class Magic; +``` + +The template class Magic object can accept unrestricted number of typename as +a formal parameter of the template, such as the following definition: + +```cpp +class Magic, + std::map>> darkMagic; +``` + +Since it is arbitrary, a template parameter with a number of 0 is also possible: `class Magic<> nothing;`. + +If you do not want to generate 0 template parameters, you can manually define at least one template parameter: + +```cpp +template class Magic; +``` + +The variable length parameter template can also be directly adjusted to the template function. +The `printf` function in the traditional C, although it can also reach the call of an indefinite number of formal parameters, is not class safe. In addition to the variable-length parameter functions that define class safety, C++11 can also make printf-like functions naturally handle objects that are not self-contained. In addition to the use of `...` in the template parameters to indicate the indefinite length of the template parameters, the function parameters also use the same representation to represent the indefinite length parameters, which provides a convenient means for us to simply write variable length parameter functions, such as: + +```cpp +template void printf(const std::string &str, Args... args); +``` + +Then we define variable length template parameters, +how to unpack the parameters? + +First, we can use `sizeof...` to calculate the number of arguments: + +```cpp +template +void magic(Ts... args) { + std::cout << sizeof...(args) << std::endl; +} +``` + +We can pass any number of arguments to the `magic` function: + +```cpp +magic(); // 0 +magic(1); // 1 +magic(1, ""); // 2 +``` + +Second, the parameters are unpacked. So far there is no simple way to process +the parameter package, but there are two classic processing methods: + +**1. Recursive template function** + +Recursion is a very easy way to think of and the most classic approach. This method continually recursively passes template parameters to the function, thereby achieving the purpose of recursively traversing all template parameters: + + +```cpp +#include +template +void printf1(T0 value) { + std::cout << value << std::endl; +} +template +void printf1(T value, Ts... args) { + std::cout << value << std::endl; + printf1(args...); +} +int main() { + printf1(1, 2, "123", 1.1); + return 0; +} +``` + +**2. Variable parameter template expansion** + +You should feel that this is very cumbersome. Added support for variable parameter template expansion in C++17, so you can write `printf` in a function: + +```cpp +template +void printf2(T0 t0, T... t) { + std::cout << t0 << std::endl; + if constexpr (sizeof...(t) > 0) printf2(t...); +} +``` + +> In fact, sometimes we use variable parameter templates, but we don't necessarily need to traverse the parameters one by one. We can use the features of `std::bind` and perfect forwarding to achieve the binding of functions and parameters, thus achieving success. The purpose of the call. + +**3. Initialize list expansion** + +Recursive template functions are a standard practice, but the obvious drawback is that you must define a function that terminates recursion. + +Here is a description of the black magic that is expanded using the initialization list: + +```cpp +template +auto printf3(T value, Ts... args) { + std::cout << value << std::endl; + (void) std::initializer_list{([&args] { + std::cout << args << std::endl; + }(), value)...}; +} +``` + +In this code, the initialization list provided in C++11 and the properties of the Lambda expression (mentioned in the next section) are additionally used. + +By initializing the list, `(lambda expression, value)...` will be expanded. Due to the appearance of the comma expression, the previous lambda expression is executed first, and the output of the parameter is completed. +To avoid compiler warnings, we can explicitly convert `std::initializer_list` to `void`. + +### Fold expression + +In C++ 17, this feature of the variable length parameter is further brought to the expression, consider the following example: + +```cpp +#include +template +auto sum(T ... t) { + return (t + ...); +} +int main() { + std::cout << sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) << std::endl; +} +``` + +### Non-type template parameter deduction + +What we mainly mentioned above is a form of template parameters: type template parameters. + +```cpp +template + auto add(T t, U u) { + return t+u; +} +``` + +The parameters of the template `T` and `U` are specific types. +But there is also a common form of template parameter that allows different literals +to be template parameters, ie non-type template parameters: + +```cpp +template +class buffer_t { +public: + T& alloc(); + void free(T& item); +private: + T data[BufSize]; +} + +buffer_t buf; // 100 as template parameter +``` + +In this form of template parameters, we can pass `100` as a parameter to the template. +After C++11 introduced the feature of type derivation, we will naturally ask, since the template parameters here +Passing with a specific literal, can the compiler assist us in type derivation, +By using the placeholder `auto`, there is no longer a need to explicitly specify the type? +Fortunately, C++17 introduces this feature, and we can indeed use the `auto` keyword to let the compiler assist in the completion of specific types of derivation. +E.g: + +```cpp +template void foo() { + std::cout << value << std::endl; + return; +} + +int main() { + foo<10>(); // value as int +} +``` + +## 2.6 Object-oriented + +### Delegate constructor + +C++11 introduces the concept of a delegate construct, which allows a constructor to call another constructor +in a constructor in the same class, thus simplifying the code: + +```cpp +class Base { +public: + int value1; + int value2; + Base() { + value1 = 1; + } + Base(int value) : Base() { // delegate Base() constructor + value2 = value; + } +}; + +int main() { + Base b(2); + std::cout << b.value1 << std::endl; + std::cout << b.value2 << std::endl; +} +``` + +### Inheritance constructor + +In traditional C++, constructors need to pass arguments one by one if they need inheritance, which leads to inefficiency. C++11 introduces the concept of inheritance constructors using the keyword using: + +```cpp +class Base { +public: + int value1; + int value2; + Base() { + value1 = 1; + } + Base(int value) : Base() { // delegate Base() constructor + value2 = value; + } +}; +class Subclass : public Base { +public: + using Base::Base; // inhereit constructor +}; +int main() { + Subclass s(3); + std::cout << s.value1 << std::endl; + std::cout << s.value2 << std::endl; +} +``` + +### Explicit virtual function overwrite + +In traditional C++, it is often prone to accidentally overloading virtual functions. E.g: + +```cpp +struct Base { + virtual void foo(); +}; +struct SubClass: Base { + void foo(); +}; +``` + +`SubClass::foo` may not be a programmer trying to overload a virtual function, just adding a function with the same name. Another possible scenario is that when the virtual function of the base class is deleted, the subclass owns the old function and no longer overloads the virtual function and turns it into a normal class method, which has catastrophic consequences. + +C++11 introduces the two keywords `override` and `final` to prevent this from happening. + +### override + +When overriding a virtual function, introducing the `override` keyword will explicitly tell the compiler to overload, and the compiler will check if the base function has such a virtual function, otherwise it will not compile: + +```cpp +struct Base { + virtual void foo(int); +}; +struct SubClass: Base { + virtual void foo(int) override; // legal + virtual void foo(float) override; // illegal, no virtual function in super class +}; +``` + +### final + +`final` is to prevent the class from being continued to inherit and to terminate +the virtual function to continue to be overloaded. + +```cpp +struct Base { + virtual void foo() final; +}; +struct SubClass1 final: Base { +}; // legal + +struct SubClass2 : SubClass1 { +}; // illegal, SubClass1 has final + +struct SubClass3: Base { + void foo(); // illegal, foo has final +}; +``` + +### Explicit delete default function + +In traditional C++, if the programmer does not provide it, the compiler will default to generating default constructors, copy constructs, assignment operators, and destructors for the object. In addition, C++ also defines operators such as `new` `delete` for all classes. This part of the function can be overridden when the programmer needs it. + +This raises some requirements: the ability to accurately control the generation of default functions cannot be controlled. For example, when copying a class is prohibited, the copy constructor and the assignment operator must be declared as `private`. Trying to use these undefined functions will result in compilation or link errors, which is a very unconventional way. + +Also, the default constructor generated by the compiler cannot exist at the same time as the user-defined constructor. If the user defines any constructor, the compiler will no longer generate the default constructor, but sometimes we want to have both constructors at the same time, which is awkward. + +C++11 provides a solution to the above requirements, allowing explicit declarations to take or reject functions that come with the compiler. E.g: + +```cpp +class Magic { + public: + Magic() = default; // explicit let compiler use default constructor + Magic& operator=(const Magic&) = delete; // explicit declare refuse constructor + Magic(int magic_number); +} +``` + +### Strongly typed enumerations + +In traditional C++, enumerated types are not type-safe, and enumerated types are treated as integers, which allows two completely different enumerated types to be directly compared (although the compiler gives the check, but not all) , ** Even the enumeration value names of different enum types in the same namespace cannot be the same**, which is usually not what we want to see. + +C++11 introduces an enumeration class and declares it using the syntax of `enum class`: + +```cpp +enum class new_enum : unsigned int { + value1, + value2, + value3 = 100, + value4 = 100 +}; +``` + +The enumeration thus defined implements type safety. First, it cannot be implicitly converted to an integer, nor can it be compared to integer numbers, and it is even less likely to compare enumerated values of different enumerated types. But if the values specified are the same between the same enumerated values, then you can compare: + +```cpp +if (new_enum::value3 == new_enum::value4) { // true + std::cout << "new_enum::value3 == new_enum::value4" << std::endl; +} +``` + +In this syntax, the enumeration type is followed by a colon and a type keyword to specify the type of the enumeration value in the enumeration, which allows us to assign a value to the enumeration (int is used by default when not specified). + +And we want to get the value of the enumeration value, we will have to explicitly type conversion, but we can overload the `<<` operator to output, you can collect the following code snippet: + +```cpp +#include +template +std::ostream& operator<<(typename std::enable_if::value, std::ostream>::type& stream, const T& e) +{ + return stream << static_cast::type>(e); +} +``` + +At this point, the following code will be able to be compiled: + +```cpp +std::cout << new_enum::value3 << std::endl +``` + +## Conclusion + +This section introduces the enhancements to language usability in modern C++, which I believe are the most important features that almost everyone needs to know and use: + +1. auto type derivation +2. Scope for iteration +3. Initialization list +4. Variable parameter template + +## Exercises + +1. Using structured binding, implement the following functions with just one line of function code: + + ```cpp + template + void update(std::map& m, F foo) { + // TODO: + } + int main() { + std::map m { + {"a", 1}, + {"b", 2}, + {"c", 3} + }; + update(m, [](std::string key) { + return std::hash{}(key); + }); + for (auto&& [key, value] : m) + std::cout << key << ":" << value << std::endl; + } + ``` + +2. Try to implement a function for calculating the mean with [Fold Expression](#Fold-expression), allowing any arguments to be passed in. + +> Refer to the answer [see this](../../exercises/2). + +[Table of Content](./toc.md) | [Previous Chapter](./01-intro.md) | [Next Chapter: Language Runtime Enhancements](./03-runtime.md) ## Licenses diff --git a/book/zh-cn/02-usability.md b/book/zh-cn/02-usability.md index ea67825f..d4fb807b 100644 --- a/book/zh-cn/02-usability.md +++ b/book/zh-cn/02-usability.md @@ -112,7 +112,7 @@ int main() { std::cout << fibonacci(10) << std::endl; // 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 - + std::cout << fibonacci(10) << std::endl; return 0; } ``` @@ -131,7 +131,7 @@ constexpr int fibonacci(const int n) { } ``` -从 C++14 开始,constexptr 函数可以在内部使用局部变量、循环和分支等简单语句,例如下面的代码在 C++11 的标准下是不能够通过编译的: +从 C++14 开始,constexpr 函数可以在内部使用局部变量、循环和分支等简单语句,例如下面的代码在 C++11 的标准下是不能够通过编译的: ```cpp constexpr int fibonacci(const int n) { @@ -195,7 +195,12 @@ if (const std::vector::iterator itr = std::find(vec.begin(), vec.end(), 3); ### 初始化列表 -初始化是一个非常重要的语言特性,最常见的就是在对象进行初始化时进行使用。在传统 C++ 中,不同的对象有着不同的初始化方法,例如普通数组、POD (**P**lain **O**ld **D**ata,即没有构造、析构和虚函数的类或结构体)类型都可以使用 `{}` 进行初始化,也就是我们所说的初始化列表。而对于类对象的初始化,要么需要通过拷贝构造、要么就需要使用 `()` 进行。这些不同方法都针对各自对象,不能通用。例如: +初始化是一个非常重要的语言特性,最常见的就是在对象进行初始化时进行使用。 +在传统 C++ 中,不同的对象有着不同的初始化方法,例如普通数组、 +POD (**P**lain **O**ld **D**ata,即没有构造、析构和虚函数的类或结构体) +类型都可以使用 `{}` 进行初始化,也就是我们所说的初始化列表。 +而对于类对象的初始化,要么需要通过拷贝构造、要么就需要使用 `()` 进行。 +这些不同方法都针对各自对象,不能通用。例如: ```cpp #include @@ -258,15 +263,15 @@ public: magicFoo.foo({6,7,8,9}); ``` - 其次,C++11 还提供了统一的语法来初始化任意的对象,例如: + ```cpp Foo foo2 {3, 4}; ``` ### 结构化绑定 -结构化绑定提供了类似其他语言中提供的多返回值的功能。在[标准库扩充:容器]()一章中,我们会学到 C++11 新增了 `std::tuple` 容器用于构造一个元组,进而囊括多个返回值。但缺陷是,C++11/14 并没有提供一种简单的方法直接从元组中拿到并定义元组中的元素,尽管我们可以使用 `std::tie` 对元组进行拆包,但我们依然必须非常清楚这个元组包含多少个对象,各个对象是什么类型,非常麻烦。 +结构化绑定提供了类似其他语言中提供的多返回值的功能。在容器一章中,我们会学到 C++11 新增了 `std::tuple` 容器用于构造一个元组,进而囊括多个返回值。但缺陷是,C++11/14 并没有提供一种简单的方法直接从元组中拿到并定义元组中的元素,尽管我们可以使用 `std::tie` 对元组进行拆包,但我们依然必须非常清楚这个元组包含多少个对象,各个对象是什么类型,非常麻烦。 C++17 完善了这一设定,给出的结构化绑定可以让我们写出这样的代码: @@ -401,9 +406,10 @@ type z == type x ```cpp template R add(T x, U y) { -return x+y + return x+y } ``` + > 注意:typename 和 class 在模板参数列表中没有区别,在 typename 这个关键字出现之前,都是使用 class 来定义模板参数的。但在模板中定义有[嵌套依赖类型](http://en.cppreference.com/w/cpp/language/dependent_name#The_typename_disambiguator_for_dependent_names)的变量时,需要用 typename 消除歧义 @@ -454,7 +460,6 @@ std::cout << "q: " << q << std::endl; > 要理解它你需要知道 C++ 中参数转发的概念,我们会在[语言运行时强化](./03-runtime.md)一章中详细介绍,你可以到时再回来看这一小节的内容。 - 简单来说,`decltype(auto)` 主要用于对转发函数或封装的返回类型进行推导,它使我们无需显式的指定 `decltype` 的参数表达式。考虑看下面的例子,当我们需要对下面两个函数进行封装时: ```cpp @@ -491,7 +496,7 @@ decltype(auto) look_up_a_string_2() { 正如本章开头出,我们知道了 C++11 引入了 `constexpr` 关键字,它将表达式或函数编译为常量结果。一个很自然的想法是,如果我们把这一特性引入到条件判断中去,让代码在编译时就完成分支判断,岂不是能让程序效率更高?C++17 将 `constexpr` 这个关键字引入到 `if` 语句中,允许在代码中声明常量表达式的判断条件,考虑下面的代码: -```Cpp +```cpp #include template @@ -636,7 +641,10 @@ auto add(T x, U y) -> decltype(x+y) { ### 变长参数模板 -模板一直是 C++ 所独有的**黑魔法**(一起念:**Dark Magic**)之一。在 C++11 之前,无论是类模板还是函数模板,都只能按其指定的样子,接受一组固定数量的模板参数;而 C++11 加入了新的表示方法,允许任意个数、任意类别的模板参数,同时也不需要在定义时将参数的个数固定。 +模板一直是 C++ 所独有的**黑魔法**(一起念:**Dark Magic**)之一。 +在 C++11 之前,无论是类模板还是函数模板,都只能按其指定的样子, +接受一组固定数量的模板参数;而 C++11 加入了新的表示方法, +允许任意个数、任意类别的模板参数,同时也不需要在定义时将参数的个数固定。 ```cpp template class Magic; @@ -659,7 +667,13 @@ class Magic class Magic; ``` -变长参数模板也能被直接调整到到模板函数上。传统 C 中的 printf 函数,虽然也能达成不定个数的形参的调用,但其并非类别安全。而 C++11 除了能定义类别安全的变长参数函数外,还可以使类似 printf 的函数能自然地处理非自带类别的对象。除了在模板参数中能使用 `...` 表示不定长模板参数外,函数参数也使用同样的表示法代表不定长参数,这也就为我们简单编写变长参数函数提供了便捷的手段,例如: +变长参数模板也能被直接调整到到模板函数上。传统 C 中的 `printf` 函数, +虽然也能达成不定个数的形参的调用,但其并非类别安全。 +而 C++11 除了能定义类别安全的变长参数函数外, +还可以使类似 printf 的函数能自然地处理非自带类别的对象。 +除了在模板参数中能使用 `...` 表示不定长模板参数外, +函数参数也使用同样的表示法代表不定长参数, +这也就为我们简单编写变长参数函数提供了便捷的手段,例如: ```cpp template void printf(const std::string &str, Args... args); @@ -675,6 +689,7 @@ void magic(Ts... args) { std::cout << sizeof...(args) << std::endl; } ``` + 我们可以传递任意个参数给 `magic` 函数: ```cpp @@ -906,13 +921,21 @@ struct SubClass3: Base { ### 显式禁用默认函数 -在传统 C++ 中,如果程序员没有提供,编译器会默认为对象生成默认构造函数、复制构造、赋值算符以及析构函数。另外,C++ 也为所有类定义了诸如 `new` `delete` 这样的运算符。当程序员有需要时,可以重载这部分函数。 +在传统 C++ 中,如果程序员没有提供,编译器会默认为对象生成默认构造函数、 +复制构造、赋值算符以及析构函数。 +另外,C++ 也为所有类定义了诸如 `new` `delete` 这样的运算符。 +当程序员有需要时,可以重载这部分函数。 -这就引发了一些需求:无法精确控制默认函数的生成行为。例如禁止类的拷贝时,必须将复制构造函数与赋值算符声明为 `private`。尝试使用这些未定义的函数将导致编译或链接错误,则是一种非常不优雅的方式。 +这就引发了一些需求:无法精确控制默认函数的生成行为。 +例如禁止类的拷贝时,必须将复制构造函数与赋值算符声明为 `private`。 +尝试使用这些未定义的函数将导致编译或链接错误,则是一种非常不优雅的方式。 -并且,编译器产生的默认构造函数与用户定义的构造函数无法同时存在。若用户定义了任何构造函数,编译器将不再生成默认构造函数,但有时候我们却希望同时拥有这两种构造函数,这就造成了尴尬。 +并且,编译器产生的默认构造函数与用户定义的构造函数无法同时存在。 +若用户定义了任何构造函数,编译器将不再生成默认构造函数, +但有时候我们却希望同时拥有这两种构造函数,这就造成了尴尬。 -C++11 提供了上述需求的解决方案,允许显式的声明采用或拒绝编译器自带的函数。例如: +C++11 提供了上述需求的解决方案,允许显式的声明采用或拒绝编译器自带的函数。 +例如: ```cpp class Magic { @@ -927,7 +950,7 @@ class Magic { 在传统 C++中,枚举类型并非类型安全,枚举类型会被视作整数,则会让两种完全不同的枚举类型可以进行直接的比较(虽然编译器给出了检查,但并非所有),**甚至同一个命名空间中的不同枚举类型的枚举值名字不能相同**,这通常不是我们希望看到的结果。 -C++11 引入了枚举类(enumaration class),并使用 `enum class` 的语法进行声明: +C++11 引入了枚举类(enumeration class),并使用 `enum class` 的语法进行声明: ```cpp enum class new_enum : unsigned int { @@ -938,7 +961,8 @@ enum class new_enum : unsigned int { }; ``` -这样定义的枚举实现了类型安全,首先他不能够被隐式的转换为整数,同时也不能够将其与整数数字进行比较,更不可能对不同的枚举类型的枚举值进行比较。但相同枚举值之间如果指定的值相同,那么可以进行比较: +这样定义的枚举实现了类型安全,首先他不能够被隐式的转换为整数,同时也不能够将其与整数数字进行比较, +更不可能对不同的枚举类型的枚举值进行比较。但相同枚举值之间如果指定的值相同,那么可以进行比较: ```cpp if (new_enum::value3 == new_enum::value4) { @@ -968,7 +992,7 @@ std::cout << new_enum::value3 << std::endl ## 总结 -本节介绍了 C++11/14/17/20 中对语言可用性的增强,其中笔者认为最为重要的几个特性是几乎所有人都需要了解并熟练使用的: +本节介绍了现代 C++ 中对语言可用性的增强,其中笔者认为最为重要的几个特性是几乎所有人都需要了解并熟练使用的: 1. auto 类型推导 2. 范围 for 迭代 @@ -1004,11 +1028,6 @@ std::cout << new_enum::value3 << std::endl [返回目录](./toc.md) | [上一章](./01-intro.md) | [下一章 运行时强化](./03-runtime.md) -## 进一步阅读的参考文献 - -1. [深入理解 C++11: C++11 新特性解析与应用. Michael Wong, IBM XL 编译器中国开发团队著](https://www.amazon.cn/dp/B00ETOV2OQ/ref=sr_1_1?ie=UTF8&qid=1522429506&sr=8-1&keywords=%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3+C%2B%2B11%3A+C%2B%2B11+%E6%96%B0%E7%89%B9%E6%80%A7%E8%A7%A3%E6%9E%90%E4%B8%8E%E5%BA%94%E7%94%A8) -2. [深入应用 C++11: 代码优化与工程级应用. 祁宇著](https://www.amazon.cn/dp/B00YGVLVA2/ref=sr_1_1?ie=UTF8&qid=1522429525&sr=8-1&keywords=%E6%B7%B1%E5%85%A5%E5%BA%94%E7%94%A8+C%2B%2B11%3A+%E4%BB%A3%E7%A0%81%E4%BC%98%E5%8C%96%E4%B8%8E%E5%B7%A5%E7%A8%8B%E7%BA%A7%E5%BA%94%E7%94%A8) - ## 许可 知识共享许可协议 diff --git a/code/2/todo/2.7.cpp b/code/2/2.19.delegate.constructor.cpp similarity index 75% rename from code/2/todo/2.7.cpp rename to code/2/2.19.delegate.constructor.cpp index 9a636d61..d1748d89 100644 --- a/code/2/todo/2.7.cpp +++ b/code/2/2.19.delegate.constructor.cpp @@ -1,11 +1,11 @@ // -// 2.7.cpp +// 2.19.constructor.cpp +// chapter 2 language usability // modern c++ tutorial // // created by changkun at changkun.de // https://github.com/changkun/modern-cpp-tutorial // -// 面向对象增强 #include #include @@ -18,12 +18,12 @@ class Base { str = s; } - // 委托构造 + // delegate constructor Base(std::string s, int v) : Base(s) { value = v; } - // 终止重载 + // final constructor virtual void foo() final { return; } @@ -36,26 +36,26 @@ class Subclass final : public Base { double floating; Subclass() = delete; - // 继承构造 + // inherit constructor Subclass(double f, int v, std::string s) : Base(s, v) { floating = f; } - // 显式重载 + // explifict constructor virtual void foo(int v) override { std::cout << v << std::endl; value = v; } -}; // 合法 final +}; // legal final // class Subclass2 : Subclass { -// }; // 非法, Subclass 已 final +// }; // illegal, Subclass has final // class Subclass3 : Base { -// void foo(); // 非法, foo 已 final +// void foo(); // illegal, foo has final // } int main() { - // Subclass oops; // 非法, 默认构造已删除 + // Subclass oops; // illegal, default constructor has deleted Subclass s(1.2, 3, "abc"); s.foo(1); diff --git a/code/2/todo/2.8.cpp b/code/2/2.20.strong.type.enum.cpp similarity index 85% rename from code/2/todo/2.8.cpp rename to code/2/2.20.strong.type.enum.cpp index 2e87f57f..ad7e73ab 100644 --- a/code/2/todo/2.8.cpp +++ b/code/2/2.20.strong.type.enum.cpp @@ -1,11 +1,10 @@ // -// 2.8.cpp +// 2.20.strong.type.enum.cpp // modern c++ tutorial // // created by changkun at changkun.de // https://github.com/changkun/modern-cpp-tutorial // -// 强类型枚举 #include template @@ -14,7 +13,7 @@ std::ostream& operator<<(typename std::enable_if::value, std::os return stream << static_cast::type>(e); } -// 如果两个都定义为 value1 和 value2,将引发重定义错误 +// there will be compile error if all define value1 和 value2 enum Left { left_value1 = 1, left_value2 @@ -37,8 +36,8 @@ int main() { std::cout << "Left::value1 == Right::value2" << std::endl; } - // 引发编译错误 - // if(new_enum::value1 == 1) { + // compile error + // if(new_enum::left_value1 == 1) { // std::cout << "true!" << std::endl; // } if (new_enum::value3 == new_enum::value4) {