|
| 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) |
0 commit comments