Skip to content

Commit c16c9a3

Browse files
authored
docs(cpp11): add chapters 03-08 (zh + en, 6 chapters x 2 langs) (#47)
Fills the cpp11 documentation gap between ch02 and ch09 with 12 new tutorial files, all following the established template (top language switcher, resource table with cppreference + markdown + practice-code links, "为什么引入?" / "Why was it introduced?" Q&A opening, four numbered sections, fixed footer). Each chapter's content is driven by the actual dslings exercises and their reference solutions: - 03 trailing-return-type: auto f() -> T syntax, deeper than ch00's introductory mention; covers cases where it's irreplaceable (decltype-on-params, lambda return type, nested-type returns) - 04 rvalue-references: T&& as a mutable binding to rvalues, value categories, the "named rvalue ref is itself an lvalue" gotcha; foundation for ch05 - 05 move-semantics: std::move as a cast (not a real move), move ctor/assign forms, valid-but-unspecified contract, rule of 0/3/5, NRVO/RVO interplay - 06 scoped-enums: enum class — scoped, no implicit int, controllable underlying type, forward-declarable; contrasted against traditional enum's leakage and implicit conversion - 07 constexpr: compile-time variables and functions, dual compile/run-time nature, C++11 single-return restriction, used in array dimensions / template params / static_assert - 08 literal-type: the LiteralType named requirement (constexpr ctor, trivial dtor, literal members) plus user-defined literals via operator"" _suffix, mapping to exercises 0 (concept) and 1 (defining your own literal type) SUMMARY.md updated in both zh and en to insert the 6 entries between 02 and 09. Authored via 6 parallel sub-agents, each handling one chapter's zh+en pair from the same template + style references (ch00, ch02, ch11) and the chapter's exercise/solution sources.
1 parent 0760698 commit c16c9a3

14 files changed

Lines changed: 2367 additions & 0 deletions

book/en/src/SUMMARY.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
- [Type Deduction - auto and decltype](./cpp11/00-auto-and-decltype.md)
1313
- [Defaulted and Deleted Functions](./cpp11/01-default-and-delete.md)
1414
- [final and override - Explicit Control of Virtual Function Behavior](./cpp11/02-final-and-override.md)
15+
- [Trailing Return Type](./cpp11/03-trailing-return-type.md)
16+
- [Rvalue References](./cpp11/04-rvalue-references.md)
17+
- [Move Semantics](./cpp11/05-move-semantics.md)
18+
- [Scoped Enums](./cpp11/06-scoped-enums.md)
19+
- [constexpr - Compile-Time Computation](./cpp11/07-constexpr.md)
20+
- [Literal Types](./cpp11/08-literal-type.md)
1521
- [List Initialization](./cpp11/09-list-initialization.md)
1622
- [Delegating Constructors](./cpp11/10-delegating-constructors.md)
1723
- [Inherited Constructors](./cpp11/11-inherited-constructors.md)
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
<div align=right>
2+
3+
🌎 [中文] | [English]
4+
</div>
5+
6+
[中文]: ../../cpp11/03-trailing-return-type.html
7+
[English]: ./03-trailing-return-type.html
8+
9+
# Trailing Return Type
10+
11+
The trailing return type is a new function declaration syntax introduced in C++11: `auto func(...) -> ReturnType`. It moves the return type from before the function name to after the parameter list. This solves a problem that the traditional syntax simply cannot express - "return type that depends on parameters" - and also provides the unified syntax for explicitly specifying lambda return types.
12+
13+
| Book | Video | Code | X |
14+
| --- | --- | --- | --- |
15+
| [cppreference](https://en.cppreference.com/w/cpp/language/function) / [markdown](https://github.com/mcpp-community/d2mcpp/blob/main/book/en/src/cpp11/03-trailing-return-type.md) | [Video Explanation]() | [Practice Code](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/cpp11/03-trailing-return-type.cpp) | |
16+
17+
18+
**Why was it introduced?**
19+
20+
- In the traditional syntax, the return type is written before the function name, at which point **the parameters are not yet in scope**, so they cannot be used to deduce the return type
21+
- Template programming often needs "return type = type of some expression involving the parameters", and without this syntax, deducing such a return type with decltype is essentially impossible to write
22+
- It unifies function-signature shape: lambdas, ordinary functions, and template functions can all use the same `auto ... -> T` form
23+
24+
**How does it differ from the traditional return-type syntax?**
25+
26+
- Traditional form: `ReturnType func(Args...)`, with the return type at the front
27+
- Trailing form: `auto func(Args...) -> ReturnType`, using auto as a placeholder and putting the actual return type after `->`
28+
- The two are equivalent in most cases, but only the trailing form can **reference parameter names** after `->` - this is its truly irreplaceable capability
29+
30+
## I. Basic Usage and Scenarios
31+
32+
### Basic Syntax - auto + ->
33+
34+
Move the return type from in front of the function name to after the parameter list, joined by `->`. Use `auto` as a placeholder before the function name to indicate "the return type is given later".
35+
36+
```cpp
37+
// Traditional form
38+
int add(double a, int b) {
39+
return a + b;
40+
}
41+
42+
// Trailing-return-type form - equivalent
43+
auto add(double a, int b) -> int {
44+
return a + b;
45+
}
46+
```
47+
48+
The compiled output of these two forms is identical. For an ordinary function, the trailing form is purely a **stylistic choice** and adds no new capability.
49+
50+
### Combining decltype to Deduce a Parameter-Dependent Return Type
51+
52+
The real power of trailing return types shows up in templates. Suppose we want a generic add that supports int, double, Point, and any addable types. The return value should be the type of `a + b`, but the concrete type depends on T1 and T2.
53+
54+
The traditional form **cannot be written** here, because at the point where the return type appears, a and b are not yet in scope.
55+
56+
```cpp
57+
// Cannot be written - a and b are not declared yet here
58+
decltype(a + b) add(T1 a, T2 b);
59+
```
60+
61+
The trailing syntax moves the return type after the parameters, where a and b are already in scope, so decltype can refer to them directly.
62+
63+
```cpp
64+
template<typename T1, typename T2>
65+
auto add(const T1 &a, const T2 &b) -> decltype(a + b) {
66+
return a + b;
67+
}
68+
69+
add(1, 2); // returns int
70+
add(1.1, 2); // returns double
71+
add(1, 2.1); // returns double
72+
```
73+
74+
This is the **single irreplaceable use case** of the trailing return type: letting the return-type expression refer to parameter names.
75+
76+
> Since C++14, an ordinary function can simply be written as `auto add(...)` and the compiler will deduce the return type from the return statement, so `-> decltype(a + b)` is no longer required in most cases. But in C++11, the trailing form is still mandatory.
77+
78+
### Explicit Return Type for Lambdas
79+
80+
A lambda has no name, and therefore no choice of "where to put the return type" - it can only use the trailing syntax from the start.
81+
82+
```cpp
83+
auto add = [](double a, double b) -> int {
84+
return a + b; // explicitly truncated to int
85+
};
86+
87+
add(1.1, 2.1); // 3, not 3.2
88+
```
89+
90+
Without `-> int`, the lambda would deduce its return type as double; once explicitly annotated, the return value is converted to the specified type. This is the standard way to control a lambda's return type.
91+
92+
### Nested / Member Types as Return Types
93+
94+
When the return type is a class's nested type, the traditional form needs a fully qualified `typename Class::Inner` up front, with extra `typename` inside templates - long and verbose.
95+
96+
```cpp
97+
template<typename T>
98+
typename std::vector<T>::iterator find_first(std::vector<T> &v, T x);
99+
```
100+
101+
The trailing form lets the function name appear first and writes the return type after `->`. In some class-member-function definitions, this also avoids repeating the class name prefix.
102+
103+
```cpp
104+
struct Box {
105+
struct Inner { /* ... */ };
106+
107+
auto make() -> Inner; // return type is just Inner
108+
};
109+
110+
auto Box::make() -> Inner { // already inside the scope of Box at this point
111+
return Inner{};
112+
}
113+
```
114+
115+
## II. Important Notes
116+
117+
### auto in the Trailing Form Is Only a Placeholder
118+
119+
The auto in the trailing form is **not type deduction** - it is just a syntactic placeholder, with the actual type given after `->`. This is different from C++14's `auto func() { return ...; }`, where the compiler genuinely deduces the type.
120+
121+
```cpp
122+
auto add(double a, int b) -> int { // C++11: return type is given explicitly by -> int
123+
return a + b;
124+
}
125+
126+
auto add(double a, int b) { // C++14: return type is deduced from the return statement
127+
return a + b;
128+
}
129+
```
130+
131+
In C++11, writing `auto func()` without `->` is a compile error.
132+
133+
### When to Use Trailing vs. Traditional
134+
135+
Don't blindly convert every function to the trailing form. Some rules of thumb:
136+
137+
- Return type **depends on parameters** (e.g. decltype(a + b)) -> trailing is mandatory
138+
- Lambda needs an explicit return type -> trailing is mandatory
139+
- Member function returning a nested type, where you'd like to skip the `Class::` prefix -> trailing is cleaner
140+
- Ordinary function with a simple return type (int / void / std::string) -> the traditional form reads better; no need to switch
141+
142+
### Not Every "auto func" Form Is a Trailing Return Type
143+
144+
`auto func() -> int` (C++11 trailing) and `auto func() { return 1; }` (C++14 return-type deduction) both start with `auto`, but their semantics are completely different.
145+
146+
- The former: auto is a placeholder, the real type is given by `->`, and the compiler **does not need to see the function body**
147+
- The latter: the compiler must see the return statement to deduce the return type, so the declaration and definition cannot be cleanly separated (a header containing only the declaration cannot expose the return type)
148+
149+
When writing functions that need to be declared in a header and defined in a source file, this difference directly affects whether the code compiles at all.
150+
151+
### Return-Type Rules Still Apply
152+
153+
The trailing return type only **changes the position** - the rules of the type itself are unchanged:
154+
155+
- Cannot return a function or an array directly (need a function pointer / array pointer)
156+
- References / const qualifications deduced by decltype are preserved
157+
- When a derived class overrides a virtual function, the return type still has to satisfy the covariant-return rule
158+
159+
```cpp
160+
int arr[3];
161+
// auto func() -> int[3]; // error: cannot return an array
162+
auto func() -> int(*)[3]; // ok: returns a pointer to an array
163+
```
164+
165+
## III. Practice Code
166+
167+
### Practice Topics
168+
169+
- 0 - [Familiarize with trailing return type - ordinary function / template / lambda](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/cpp11/03-trailing-return-type.cpp)
170+
171+
### Practice Code Auto-detection Command
172+
173+
```
174+
d2x checker trailing-return-type
175+
```
176+
177+
## IV. Additional Resources
178+
179+
- [Discussion Forum](https://forum.d2learn.org/category/20)
180+
- [d2mcpp Tutorial Repository](https://github.com/mcpp-community/d2mcpp)
181+
- [Tutorial Video List](https://space.bilibili.com/65858958/lists/5208246)
182+
- [Tutorial Support Tool - xlings](https://github.com/d2learn/xlings)
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<div align=right>
2+
3+
🌎 [中文] | [English]
4+
</div>
5+
6+
[中文]: ../../cpp11/04-rvalue-references.html
7+
[English]: ./04-rvalue-references.html
8+
9+
# Rvalue References
10+
11+
Rvalue reference `T&&` is a new kind of reference introduced in C++11 that binds precisely to **rvalues / about-to-expire objects**. It lets the compiler distinguish "a temporary whose resources can be stolen" from "a named object that must be preserved" during overload resolution, and it is the syntactic foundation for move semantics and perfect forwarding.
12+
13+
| Book | Video | Code | X |
14+
| --- | --- | --- | --- |
15+
| [cppreference](https://en.cppreference.com/w/cpp/language/reference) / [markdown](https://github.com/mcpp-community/d2mcpp/blob/main/book/en/src/cpp11/04-rvalue-references.md) | [Video Explanation]() | [Practice Code](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/cpp11/04-rvalue-references.cpp) | |
16+
17+
18+
**Why was it introduced?**
19+
20+
- Before C++11, the only way to bind a temporary was `const T&`, which gave a read-only view — there was no way to reuse its resources without copying
21+
- There was no mechanism that could "recognize an rvalue" at the overload-resolution level, so the compiler could not separate "constructed from a temporary" from "constructed from a normal object"
22+
- It provides the syntactic foundation for move semantics (`std::move`) and perfect forwarding (`std::forward`), making "stealing resources from an expiring object" something the language can express
23+
24+
**What's the difference between lvalues and rvalues?**
25+
26+
- **lvalue**: an expression with a name, persistent storage, and an addressable identity — for example a declared variable
27+
- **rvalue**: usually a literal, a temporary, or a non-reference function return value — short-lived and not directly addressable
28+
- Rule of thumb: **if it can appear on the left of `=` and you can take its address with `&`**, it's an lvalue; otherwise it's most likely an rvalue
29+
30+
## I. Basic Usage and Scenarios
31+
32+
### Telling lvalues and rvalues Apart
33+
34+
To classify an expression, ask whether it has "a name + a persistent identity".
35+
36+
```cpp
37+
int a = 1; // a is an lvalue
38+
int b = a + 1; // a + 1 is an rvalue (no name, just a temporary result)
39+
40+
&a; // ok: an lvalue is addressable
41+
// &(a + 1); // error: an rvalue is not addressable
42+
43+
int &lref = a; // ok: lvalue reference binds an lvalue
44+
// int &lref2 = a + 1; // error: a plain lvalue reference cannot bind an rvalue
45+
```
46+
47+
### Declaring and Binding an Rvalue Reference
48+
49+
`T&&` is the rvalue-reference syntax. It **only binds to rvalues**.
50+
51+
```cpp
52+
int &&rref1 = 10; // ok: a literal is an rvalue
53+
int &&rref2 = a + 1; // ok: a temporary computation is an rvalue
54+
55+
// int &&rref3 = a; // error: an rvalue reference cannot bind an lvalue directly
56+
```
57+
58+
Once bound, the temporary's lifetime is **extended** to the end of the reference variable's scope — same as the rule for `const T&` — except that an rvalue reference gives you a **mutable view**.
59+
60+
```cpp
61+
struct Object {
62+
int data = 0;
63+
};
64+
65+
const Object &cref = Object(); // lifetime extended, but read-only
66+
// cref.data = 1; // error: cannot mutate through a const reference
67+
68+
Object &&rref = Object(); // lifetime extended, and writable
69+
rref.data = 1; // ok
70+
```
71+
72+
This is exactly what the practice code is verifying: `objRef.data = 1;` must compile, while `&objRef` still points to the same temporary whose lifetime was extended.
73+
74+
### Distinguishing lvalues and rvalues in Overloads
75+
76+
Using an rvalue reference as a function parameter lets the compiler take two different overload paths for "passing an lvalue" vs "passing an rvalue".
77+
78+
```cpp
79+
struct Object {
80+
Object() { std::cout << "Object()\n"; }
81+
Object(const Object&) { std::cout << "Object(const Object&)\n"; }
82+
Object(Object&&) { std::cout << "Object(Object&&)\n"; }
83+
};
84+
85+
void use(const Object&) { std::cout << "use lvalue\n"; }
86+
void use(Object&&) { std::cout << "use rvalue\n"; }
87+
88+
int main() {
89+
Object a;
90+
use(a); // -> use lvalue (a is an lvalue)
91+
use(Object()); // -> use rvalue (a temporary is an rvalue)
92+
}
93+
```
94+
95+
The practice's `Object` defines both a copy constructor `Object(const Object&)` and a move constructor `Object(Object&&)` precisely so the prints tell you "which path was taken".
96+
97+
### Extending a Temporary's Lifetime
98+
99+
Here is the simplified scenario from the exercise: bind the prvalue `Object()` to a reference, and the temporary's destruction is deferred until the reference variable leaves scope.
100+
101+
```cpp
102+
{
103+
Object &&objRef = Object(); // temporary's lifetime extended to here
104+
objRef.data = 1; // mutate it through the rvalue reference
105+
} // destructor runs here
106+
```
107+
108+
Switching to `const Object &objRef = Object();` extends the lifetime in the same way, but the line `objRef.data = 1;` would no longer compile — that is the most direct difference between `const T&` and `T&&` in this scenario.
109+
110+
## II. Important Notes
111+
112+
### An Rvalue Reference Variable Itself Is an lvalue
113+
114+
In `int &&rref = 10;`, the **name** `rref` has identity and is addressable, so when used in an expression it is an **lvalue** — no longer an rvalue.
115+
116+
```cpp
117+
void use(const Object&) { std::cout << "lvalue path\n"; }
118+
void use(Object&&) { std::cout << "rvalue path\n"; }
119+
120+
Object &&rref = Object();
121+
use(rref); // -> lvalue path (rref is an lvalue in expressions!)
122+
```
123+
124+
If you want to pass it on as an rvalue again, you must explicitly cast with `std::move(rref)` — which is the entry point into the next chapter on move semantics.
125+
126+
### Overload Resolution Between const Reference and Rvalue Reference
127+
128+
When both `const T&` and `T&&` overloads exist, the compiler prefers the `T&&` version for an **rvalue argument**.
129+
130+
```cpp
131+
void f(const Object&) { std::cout << "const &\n"; }
132+
void f(Object&&) { std::cout << "&&\n"; }
133+
134+
f(Object()); // -> && (rvalue prefers the rvalue reference)
135+
136+
Object a;
137+
f(a); // -> const & (lvalue matches the const lvalue reference)
138+
```
139+
140+
This rule is what allows STL containers (such as `std::vector::push_back`) to take separate "copy" and "move" paths depending on the value category of the argument.
141+
142+
### Don't Confuse `T&&` With "Universal Reference"
143+
144+
In **template argument deduction**, `T&&` becomes a "universal / forwarding reference", which behaves differently from a plain rvalue reference — the same syntax in `template<typename T> void f(T&&)` accepts both lvalues and rvalues. That belongs to perfect forwarding and is covered later. For now, just remember: in **non-template contexts**, `T&&` is an rvalue reference and accepts only rvalues.
145+
146+
```cpp
147+
void g(Object&& o); // accepts rvalues only
148+
template<typename T> void h(T&&); // universal reference, accepts both
149+
```
150+
151+
### Rvalue References Are the Entry Point of Move Semantics, Not the Whole Story
152+
153+
This chapter focuses on the **value-category + reference-binding** layer. The part where rvalue references actually "steal resources" (move constructor / move assignment / `std::move`) is covered in ch05. That said, the move-constructor print `Object(Object&&)` in the practice already lets you observe end-to-end how "rvalue argument -> move path" works.
154+
155+
## III. Practice Code
156+
157+
### Practice Topics
158+
159+
- 0 - [Use an rvalue reference to extend a temporary's lifetime and mutate it](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/cpp11/04-rvalue-references.cpp)
160+
161+
### Practice Code Auto-detection Command
162+
163+
```
164+
d2x checker rvalue-references
165+
```
166+
167+
## IV. Additional Resources
168+
169+
- [Discussion Forum](https://forum.d2learn.org/category/20)
170+
- [d2mcpp Tutorial Repository](https://github.com/mcpp-community/d2mcpp)
171+
- [Tutorial Video List](https://space.bilibili.com/65858958/lists/5208246)
172+
- [Tutorial Support Tool - xlings](https://github.com/d2learn/xlings)

0 commit comments

Comments
 (0)