Skip to content

Commit 6583be1

Browse files
committed
book: add 'since C++NN' markers to Chapter 2 features
Add an italic standard-version marker under each feature heading in Chapter 2 (both languages), so a reader who already knows some C++11 can see at a glance what is newer. Same convention introduced with the structured-bindings demo.
1 parent 4a7a693 commit 6583be1

2 files changed

Lines changed: 116 additions & 0 deletions

File tree

book/en-us/02-usability.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ which refers to the language behavior that occurred before the runtime.
1818

1919
### nullptr
2020

21+
*(since C++11)*
22+
2123
The purpose of `nullptr` appears to replace `NULL`. There are **null pointer constants** in the C and C++ languages,
2224
which can be implicitly converted to null pointer value of any pointer type,
2325
or null member pointer value of any pointer-to-member type in C++.
@@ -93,6 +95,8 @@ We will discuss them in detail later in the [decltype](#decltype) section.
9395

9496
### constexpr
9597

98+
*(since C++11; relaxed in C++14)*
99+
96100
C++ itself already has the concept of constant expressions, such as 1+2,
97101
3\*4. Such expressions always produce the same result without any side effects.
98102
If the compiler can directly optimize and embed these expressions into the program at
@@ -188,6 +192,8 @@ constexpr int fibonacci(const int n) {
188192

189193
### if-switch
190194

195+
*(since C++17)*
196+
191197
In traditional C++, the declaration of a variable can declare a temporary variable `int`
192198
even though it can be located anywhere, even within a `for` statement,
193199
but there is always no way to declare a temporary variable in the `if` and `switch` statements.
@@ -237,6 +243,8 @@ Is it similar to the Go?
237243

238244
### Initializer list
239245

246+
*(since C++11)*
247+
240248
Initialization is a very important language feature,
241249
the most common one is when the object is initialized.
242250
In traditional C++, different objects have different initialization methods,
@@ -378,6 +386,8 @@ C++11 introduces the two keywords `auto` and `decltype` to implement type deriva
378386
379387
### auto
380388
389+
*(since C++11)*
390+
381391
`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.
382392
383393
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:
@@ -454,6 +464,8 @@ std::cout << add20(i, j) << std::endl;
454464
455465
### decltype
456466
467+
*(since C++11)*
468+
457469
The `decltype` keyword is used to solve the defect that the `auto` keyword
458470
can only deduce the type of a variable. Its usage is very similar to `typeof`,
459471
a non-standard extension long provided by some compilers (e.g. GCC and Clang)
@@ -495,6 +507,8 @@ type z == type x
495507

496508
### tail type inference
497509

510+
*(since C++11)*
511+
498512
You may think that whether `auto` can be used to deduce the return type of a function. Still consider an example of an add function, which we have to write in traditional C++:
499513

500514
```cpp
@@ -559,6 +573,8 @@ std::cout << "q: " << q << std::endl;
559573

560574
### decltype(auto)
561575

576+
*(since C++14)*
577+
562578
`decltype(auto)` is a slightly more complicated use of C++14.
563579

564580
> To understand it you need to know the concept of parameter forwarding
@@ -604,6 +620,8 @@ decltype(auto) look_up_a_string_2() {
604620
605621
### if constexpr
606622
623+
*(since C++17)*
624+
607625
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:
608626
609627
```cpp
@@ -640,6 +658,8 @@ int main() {
640658
641659
### Range-based for loop
642660
661+
*(since C++11)*
662+
643663
Finally, C++11 introduces a range-based iterative method, and we can write loops that are as concise
644664
as Python, and we can further simplify the previous example:
645665
@@ -689,6 +709,8 @@ C++ templates have always been a special art of the language, and templates can
689709

690710
### Extern templates
691711

712+
*(since C++11)*
713+
692714
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.
693715

694716
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:
@@ -700,6 +722,8 @@ extern template class std::vector<double>; // should not instantiation in curren
700722
701723
### The ">"
702724
725+
*(since C++11)*
726+
703727
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:
704728
705729
```cpp
@@ -723,6 +747,8 @@ std::vector<MagicType<(1>2)>> magic; // legal, but not recommended
723747
724748
### Type alias templates
725749
750+
*(since C++11)*
751+
726752
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:
727753
728754
```cpp
@@ -755,6 +781,8 @@ int main() {
755781
756782
### Variadic templates
757783
784+
*(since C++11)*
785+
758786
The template has always been one of C++'s unique **Black Magic**.
759787
In traditional C++,
760788
both a class template and a function template could only accept
@@ -874,6 +902,8 @@ To avoid compiler warnings, we can explicitly convert `std::initializer_list` to
874902

875903
### Fold expression
876904

905+
*(since C++17)*
906+
877907
In C++ 17, this feature of the variable length parameter is further brought to the expression, consider the following example:
878908

879909
```cpp
@@ -889,6 +919,8 @@ int main() {
889919
890920
### Non-type template parameter deduction
891921
922+
*(since C++17)*
923+
892924
What we mainly mentioned above is a form of template parameters: type template parameters.
893925
894926
```cpp
@@ -935,6 +967,8 @@ int main() {
935967

936968
### SFINAE and `std::enable_if`
937969

970+
*(since C++11)*
971+
938972
SFINAE stands for "Substitution Failure Is Not An Error". It describes the rule that, when substituting template arguments produces an invalid type or expression in the **immediate context**, the compiler does not raise an error but silently removes that candidate from the overload set. This was the main way to constrain template parameters before C++20's concepts.
939973

940974
The most common tool is `std::enable_if` from `<type_traits>`. The `describe` below is visible only for integral types:
@@ -969,6 +1003,8 @@ SFINAE is powerful but obscure to write and produces verbose error messages. C++
9691003

9701004
### Delegate constructor
9711005

1006+
*(since C++11)*
1007+
9721008
C++11 introduces the concept of a delegate construct, which allows a constructor to call another constructor
9731009
in a constructor in the same class, thus simplifying the code:
9741010

@@ -995,6 +1031,8 @@ int main() {
9951031
9961032
### Inheritance constructor
9971033
1034+
*(since C++11)*
1035+
9981036
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:
9991037
10001038
```cpp
@@ -1023,6 +1061,8 @@ int main() {
10231061

10241062
### Explicit virtual function overwrite
10251063

1064+
*(since C++11)*
1065+
10261066
In traditional C++, it is often prone to accidentally overloading virtual functions. E.g:
10271067

10281068
```cpp
@@ -1074,6 +1114,8 @@ struct SubClass3: Base {
10741114
10751115
### Explicit delete default function
10761116
1117+
*(since C++11)*
1118+
10771119
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. Besides, C++ also defines operators such as `new` `delete` for all classes. This part of the function can be overridden when the programmer needs it.
10781120
10791121
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.
@@ -1093,6 +1135,8 @@ class Magic {
10931135

10941136
### Strongly typed enumerations
10951137

1138+
*(since C++11)*
1139+
10961140
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.
10971141

10981142
C++11 introduces an enumeration class and declares it using the syntax of `enum class`:
@@ -1139,6 +1183,8 @@ std::cout << new_enum::value3 << std::endl
11391183

11401184
### Inline variables
11411185

1186+
*(since C++17)*
1187+
11421188
Before C++17, a non-const static data member of a class had to be defined separately outside the class, and defining a global variable in a header would cause duplicate-definition link errors when the header was included by multiple translation units. C++17 introduces `inline` variables, which allow a variable (including a static data member) to be defined in a header without violating the One Definition Rule (ODR), even when included by multiple translation units:
11431189

11441190
```cpp
@@ -1152,6 +1198,8 @@ This greatly simplifies writing header-only libraries.
11521198
11531199
### Nested namespace definitions
11541200
1201+
*(since C++17)*
1202+
11551203
C++17 allows writing nested namespace definitions in a single line using `::`, instead of indenting level by level:
11561204
11571205
```cpp
@@ -1172,6 +1220,8 @@ namespace A::B::C {
11721220

11731221
### constexpr lambda
11741222

1223+
*(since C++17)*
1224+
11751225
Since C++17, a lambda expression that satisfies the requirements of a constant expression is implicitly `constexpr` (and may also be explicitly marked `constexpr`), so it can be evaluated at compile time:
11761226

11771227
```cpp
@@ -1183,6 +1233,8 @@ constexpr int result = add(3, 4); // evaluated at compile time, result == 7
11831233
11841234
### Single-argument static_assert
11851235
1236+
*(since C++17)*
1237+
11861238
`static_assert` performs a compile-time assertion. Before C++17 it required a diagnostic message as its second argument; since C++17 that message is optional:
11871239
11881240
```cpp
@@ -1192,6 +1244,8 @@ static_assert(sizeof(int) >= 2, "int must be >= 2 bytes"); // a message is still
11921244

11931245
### New aggregate rules
11941246

1247+
*(since C++17)*
1248+
11951249
C++17 relaxed the definition of an aggregate: an aggregate may now have public base classes (which must themselves be aggregates), and the base subobject can be brace-initialized along with the rest:
11961250

11971251
```cpp
@@ -1203,6 +1257,8 @@ Derived d{{1}, 2}; // {a}, b — legal since C++17
12031257
12041258
### Boolean logic metafunctions
12051259
1260+
*(since C++17)*
1261+
12061262
C++17 added `std::conjunction`, `std::disjunction`, and `std::negation` to `<type_traits>` for composing other type traits with logical AND/OR/NOT at compile time (and `conjunction`/`disjunction` short-circuit):
12071263
12081264
```cpp
@@ -1219,6 +1275,8 @@ static_assert(std::negation_v<std::is_floating_point<int>>);
12191275

12201276
### `__has_include`
12211277

1278+
*(since C++17)*
1279+
12221280
C++17 standardized the preprocessor operator `__has_include`, which checks at compile time whether a header is available, enabling portable conditional inclusion:
12231281

12241282
```cpp

0 commit comments

Comments
 (0)