Skip to content

Commit eedc605

Browse files
gregmarrhsutter
andauthored
Another round of docs updates (#983)
* types * metafunctions * Taking a pass over the updates, and adding that `that` must be `in` or `move` * Remove TODO comment for now Not sure of its meaning, we can add again later --------- Co-authored-by: Herb Sutter <[email protected]>
1 parent c7eccf4 commit eedc605

File tree

2 files changed

+24
-17
lines changed

2 files changed

+24
-17
lines changed

docs/cpp2/metafunctions.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ The most important thing about metafunctions is that they are not hardwired lang
1313

1414
## Applying metafunctions
1515

16-
Metafunctions provide an easy way for a type author to opt into a group of defaults, constraints, and generated functions: Just write `@name` afer the `:` of a declaration, where `name` is the name of the metafunctions. This lets the type author declare (and the human reader see) the intent up front: "This isn't just any `type`, this is a `@value type`" which automatically gives the type default/copy/move construction and assignment, `<=>` with `std::strong_ordering` comparisons, and guarantees that it has a public destructor and no protected or virtual functions:
16+
Metafunctions provide an easy way for a type author to opt into a group of defaults, constraints, and generated functions: Just write `@name` afer the `:` of a declaration, where `name` is the name of the metafunction. This lets the type author declare (and the human reader see) the intent up front: "This isn't just any `type`, this is a `@value type`" which automatically gives the type default/copy/move construction and assignment, `<=>` with `std::strong_ordering` comparisons, and guarantees that it has a public destructor and no protected or virtual functions:
1717

1818
``` cpp title="Using the value metafunction when writing a type" hl_lines="1"
1919
point2d: @value type = {
@@ -95,7 +95,7 @@ skat_game: @enum<i16> type = {
9595

9696
Consider `hearts`: It's a member object declaration, but it doesn't have a type (or a default value) which is normally illegal, but here it's okay because the `@enum<i16>` metafunction fills them in: It iterates over all the data members and gives each one the underlying type (here explicitly specified as `i16`, otherwise it would be computed as the smallest signed type that's big enough), and an initializer (by default one higher than the previous enumerator).
9797

98-
Unlike C `enum`, this `@enum` is scoped and strongly typed (does not implicitly convert to the underlying type.
98+
Unlike C `enum`, this `@enum` is scoped and strongly typed (does not implicitly convert to the underlying type).
9999

100100
Unlike C++11 `enum class`, it's "just a `type`" which means it can naturally also have member functions and other things that a type can have:
101101

@@ -141,10 +141,11 @@ file_attributes: @flag_enum<u8> type = {
141141
// name_or_number is declaratively a safe union/variant type:
142142
// it has a discriminant that enforces only one alternative
143143
// can be active at a time, members always have a name, and
144-
// each member has .is_member() and .member() accessors...
145-
// the word "union" carries all that meaning as a convenient
146-
// and readable opt-in without hardwiring "union" specially
147-
// into the language
144+
// each member has .is_member(), .set_member(), and .member()
145+
// accessors using the member name... the word "union"
146+
// carries all that meaning as a convenient and readable
147+
// opt-in without hardwiring "union" specially into the
148+
// language
148149
//
149150
name_or_number: @union type = {
150151
name: std::string;
@@ -155,6 +156,7 @@ main: () = {
155156
x: name_or_number = ();
156157

157158
x.set_name("xyzzy"); // now x is a string
159+
assert( x.is_name() );
158160
std::cout << x.name(); // prints the string
159161

160162
// trying to use x.num() here would cause a Type safety

docs/cpp2/types.md

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ A `this` parameter of an `operator=` function can additionally be declared as:
110110

111111
## `operator=` — Construction, assignment, and destruction
112112

113-
All value operations are spelled `operator=`, including construction, assignment, and destruction. `operator=` sets the value of `this` object, so the `this` parameter can be pass as anything but `in` (which would imply `const`):
113+
All value operations are spelled `operator=`, including construction, assignment, and destruction. `operator=` sets the value of `this` object, so the `this` parameter can be passed as anything but `in` (which would imply `const`):
114114

115115
- **`out this`:** Writing `operator=: (out this /*...*/ )` is naturally both a constructor and an assignment operator, because an `out` parameter can take an uninitialized or initialized argument. If you don't write a more-specialized `inout this` assignment operator, Cpp2 will use the `out this` function also for assignment.
116116

@@ -120,11 +120,14 @@ All value operations are spelled `operator=`, including construction, assignment
120120

121121
Unifying `operator=` enables usable `out` parameters, which is essential for composable guaranteed initialization. We want the expression syntax `x = value` to be able to call a constructor or an assignment operator, so naming them both `operator=` is consistent.
122122

123+
TODO Return type of assignment operator?
124+
123125
> Note: Writing `=` always invokes an `operator=` (in fact for a Cpp2-authored type, and semantically for a Cpp1-authored type). This avoids the Cpp1 inconsistency that "writing `=` calls `operator=`, except when it doesn't" (such as in a Cpp1 variable initialization). Conversely, `operator=` is always invoked by `=` in Cpp2.
124126
127+
125128
### `that` — A source parameter
126129

127-
All functions can have a **`that`** is a synonym for the object to be copied/moved from. Like `this`, at type scope it is never declared with an explicit `: its_type` because its type is always the current type.
130+
All type-scope functions can have **`that`** as their second parameter, which is a synonym for the object to be copied/moved from. Like `this`, at type scope it is never declared with an explicit `: its_type` because its type is always the current type. Unlike `this`, `that` is always passed as a `in` (the default) or `move` parameter.
128131

129132
`that` can be an `in` (default) or `move` parameter. Which you choose naturally determines what kind of member function is being declared:
130133

@@ -157,7 +160,7 @@ In Cpp1 terms, they can be described as follows:
157160

158161
- **M2 is preferred over A2.** Both M2 and A2 can generate a missing `(inout this, move that)` function. If both options are available, Cpp2 prefers to use M2 (generate move assignment from copy assignment, which could itself have been generated from copy construction) rather than A2 (generate move assignment from move construction). This is because M2 is a better fit: Move assignment is more like copy assignment than like move construction, because assignments are designed structurally to set the value of an existing `this` object.
159162

160-
The most general `operator=` with `that` is `(out this, that)`. In Cpp1 terms, it generates all four combinations of { copy, move } x { constructor, assignment }. This is often sufficient, so you can write all these value-setting just once. If you do want to write a more specific version that does something else, though, you can always write it too.
163+
The most general `operator=` with `that` is `(out this, that)`. In Cpp1 terms, it generates all four combinations of { copy, move } x { constructor, assignment }. This is often sufficient, so you can write all these value-setting functions just once. If you do want to write a more specific version that does something else, though, you can always write it too.
161164

162165
> Note: Generating `inout this` (assignment) from `out this` also generates **converting assignment** from converting construction, which is a new thing. Today in Cpp1, if you write a converting constructor from another type `X`, you may or may not write the corresponding assignment from `X`; in Cpp2 you will get that by default, and it sets the object to the same state as the converting constructor from `X` does.
163166
@@ -169,21 +172,23 @@ There are only two defaults the language will generate implicitly for a type:
169172

170173
- The only special function every type must have is the destructor. If you don't write it by hand, a public nonvirtual destructor is generated by default.
171174

172-
- If no `operator=` functions are written by hand, a public default constructor is generated by default.
175+
- If no `operator=` functions other than the destructor are written by hand, a public default constructor is generated by default.
173176

174177
All other `operator=` functions are explicitly written, either by hand or by opting into applying a metafunction (see below).
175178

176179
> Note: Because generated functions are always opt-in, you can never get a generated function that's wrong for your type, and so Cpp2 doesn’t need to support "=delete" for the purpose of suppressing unwanted generated functions.
177180
178181
### Memberwise by default
179182

180-
All copy/move/comparison `operator=` functions are memberwise by default in Cpp2. That includes when you write memberwise construction and assignment yourself. In a hand-written `operator=`:
183+
All copy/move/comparison `operator=` functions are memberwise by default in Cpp2. That includes when you write memberwise construction and assignment yourself.
184+
185+
In a hand-written `operator=`:
181186

182-
- The body must begin with a series of `member = value;` statements, one for each of the type's data members in order.
187+
- The body must begin with a series of `member = value;` statements, one for each of the type's data members (including base classes) in declaration order.
183188

184-
- If the body does not mention a member, by default the member's default initializer is used.
189+
- If the body does not mention a member in the appropriate place in the beginning section, by default the member's default initializer is used.
185190

186-
- In an assignment operator (`inout this`), you an explicitly skip setting a member by writing `member = _;` where it would normally be set, if you know you have a reason to set its value later instead.
191+
- In an assignment operator (`inout this`), you can explicitly skip setting a member by writing `member = _;` where it would normally be set if you know you have a reason to set its value later instead or if the existing value needs to be preserved.
187192

188193
For example:
189194

@@ -224,12 +229,12 @@ main: () = {
224229
y = x; // copy assign
225230
z := (move x); // move construct
226231
z = (move y); // move assign
227-
x.print(); // [] [] - moved from
228-
y.print(); // [] [] - moved from
232+
x.print(); // "value is [] []" - moved from
233+
y.print(); // "value is [] []" - moved from
229234
}
230235
```
231236

232-
> Note: This makes memberwise semantics symmetric for construction and assignment. In Cpp1, only non-copy/move constructors have a default, which is to initialize a member with its default initializer. In Cpp2, both constructors and assignment operators default to using the default initializer for if it's a conversion function (non-`that`, aka non-copy/move), and using memberwise `member = that.member;` for copy/move functions.
237+
> Note: This makes memberwise semantics symmetric for construction and assignment. In Cpp1, only non-copy/move constructors have a default, which is to initialize a member with its default initializer. In Cpp2, both constructors and assignment operators default to using the default initializer if it's a conversion function (non-`that`, aka non-copy/move), and using memberwise `member = that.member;` for copy/move functions.
233238
234239

235240

0 commit comments

Comments
 (0)