diff --git a/docs/cpp2/types.md b/docs/cpp2/types.md index cfc9d2faa..31285be7d 100644 --- a/docs/cpp2/types.md +++ b/docs/cpp2/types.md @@ -110,7 +110,7 @@ A `this` parameter of an `operator=` function can additionally be declared as: ## `operator=` — Construction, assignment, and destruction -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`): +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`): - **`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. @@ -120,11 +120,13 @@ All value operations are spelled `operator=`, including construction, assignment 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. +TODO Return type of assignment operator? + > 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. ### `that` — A source parameter -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. +All type-scope functions can have **`that`** as its 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. `that` can be an `in` (default) or `move` parameter. Which you choose naturally determines what kind of member function is being declared: @@ -157,7 +159,7 @@ In Cpp1 terms, they can be described as follows: - **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. -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. +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. > 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. @@ -169,7 +171,7 @@ There are only two defaults the language will generate implicitly for a type: - 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. -- If no `operator=` functions are written by hand, a public default constructor is generated by default. +- If no `operator=` functions other than the destructor are written by hand, a public default constructor is generated by default. All other `operator=` functions are explicitly written, either by hand or by opting into applying a metafunction (see below). @@ -177,13 +179,15 @@ All other `operator=` functions are explicitly written, either by hand or by opt ### Memberwise by default -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=`: +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=`: -- The body must begin with a series of `member = value;` statements, one for each of the type's data members in order. +- 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. -- If the body does not mention a member, by default the member's default initializer is used. +- 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. -- 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. +- 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. For example: @@ -213,6 +217,8 @@ mytype: type print(); } + // TODO skipping the default memberwise assignment for later setting + print: (this) = std::cout << "value is [(name)$] [(social_handle)$]\n"; } @@ -224,12 +230,12 @@ main: () = { y = x; // copy assign z := (move x); // move construct z = (move y); // move assign - x.print(); // [] [] - moved from - y.print(); // [] [] - moved from + x.print(); // "value is [] []" - moved from + y.print(); // "value is [] []" - moved from } ``` -> 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. +> 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.