Date: Tue, 1 Feb 2022 18:28:59 +0100
Yes, exactly P1029R3 + operator reloc.
If we say that operator reloc invokes move-constructor and then
'removes' source object from the scope, then by having either:
a) MyType(MyType&&) = bitcopy;
b) MyType(MyType&&) { <user code> }
You will be able to get both optimal code for bitcopyable types, and
any custom behaviour for relocation you might imagine.
And all this without a need for a new special member function, and new
complex rules.
This integrates really well to existing code.
Regards,
Maciej
wt., 1 lut 2022 o 17:10 Sébastien Bini <sebastien.bini_at_[hidden]> napisał(a):
>
> Hello again,
>
> Thank you both for your comments.
>
> @Gašper Ažman :
> > I'd very much like to see sections on the comparisons with the various papers that came in the past. It'll both show you're aware of all the historical context as an author, and save the readers the trouble of having to think through all of it when reviewing.
>
> I will work on it. Thank you for pointing out the proposals on the same topic, I find it hard to search for them. Is there some central place I'm missing? I only found http://www.open-std.org/jtc1/sc22/wg21/docs/papers/ but it's not convenient to browse through all of them. Likewise, you mentioned this section should also "answer to the previously surfaced objections"; but do you know how I find them?
>
> I will also get in touch with Arthur O'Dwyer and Howard Hinnant. I see Arthur O'Dwyer is the author of one of the linked proposals but has Howard Hinnant worked on something similar?
>
> @Maciej Cencora:
> I take it your proposition is to add the reloc keyword to P1029R3. I honestly did not know of P1029R3, sorry about that. I think it's definitely something to consider.
>
> On one hand, I like the new T(T&&) = relocate/bitcopies; move constructor. I get your point, and it involves less changes in the core langage, which is a good thing.
>
> However, having a dedicated constructor for relocation allows for more freedom in the relocation implementation. In what I propose, the relocation constructor can be more than a memcpy, if needs be. I don't know if there are cases where relocation involves more than a memcpy, but it might (instance tracking?). It's something I need to ponder. But it's definitely a good point, thanks!
>
> Regards,
> Sébastien
>
>
> On Tue, Feb 1, 2022 at 12:41 PM Maciej Cencora <m.cencora_at_[hidden]> wrote:
>>
>> Hi,
>>
>> I don't think adding yet another reference type and yet another
>> special member function is a good way to solve this, as the complexity
>> in this area is already big. I think the solution should reuse as much
>> as possible of existing syntax.
>> What I think would be better, is that we kept move-constructors, but
>> we add a new syntax to mark if a class is relocatable:
>> MyClass(MyClass&& other) = relocate;
>>
>> Marking such a constructor as relocate (no user-provided definition
>> allowed), would indicate to compiler two things: 1) moving is just a
>> trivial memcpy, 2) moved-from object is left in a state (e.g. default
>> constructed) where destructor call has no-side effect.
>> So current code:
>> MyClass newObject(std::move(other));
>>
>> becomes (pseudo-code):
>> MyClass newObject = uninitialized;
>> new (&newObject) MyClass(other); // or memcpy
>> new (&other) MyClass();
>> // when 'other' goes out of scope, its destruction can be skipped
>> because it is a noop.
>>
>> So far it does not change anything w.r.t. what we have now.
>> But if we add Sebastian's proposed operator reloc with such a
>> semantics that it will call move-constructor, and mark source object
>> as already destroyed we get the semantic checking that moved-from
>> object cannot be used anymore.
>> Operator reloc can be called on any type that is move-constructible,
>> it is just that for types marked as relocatable such an operation can
>> be better optimized.
>>
>> MyClass newObject = reloc other;
>> // now other cannot be referenced any more
>>
>> Since we do not introduce new types of references, or new types of
>> member functions, we can gradually migrate code from calling std::move
>> to using reloc operator (while preserving ABI and API compatibility).
>> This will also allow to finally have a optimal construction for types
>> with user-defined constructors:
>>
>> struct Person
>> {
>> Person(std::string firstName, lastName)
>> : firstName(reloc firstName)
>> , lastName(reloc lastName)
>> {}
>>
>> std::string firstName, lastName;
>> };
>>
>> Person p1("John", "Doe"); // no temporaries, no move constructors
>> Person p2(p1.firstName, p2.lastName); // one copy, no temporaries, no
>> move constructors
>>
>> Regards,
>> Maciej
>>
>> wt., 1 lut 2022 o 11:04 Gašper Ažman via Std-Proposals
>> <std-proposals_at_[hidden]> napisał(a):
>> >
>> > Hi Sebastien,
>> >
>> > you sure made a pretty long write-up! What I'm missing on the first skim-through is a thorough review of the currently published papers in the space and answers to the previously surfaced objections.
>> >
>> > Some of the papers in this space:
>> > http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1144r5.html
>> > http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4158.pdf
>> > http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1029r3.pdf
>> > http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0023r0.pdf
>> >
>> > I also couldn't find any way that your proposal handles maybe-destroy references. For instance, how do you handle
>> >
>> > bool maybe_destroy(T& x) {
>> > if (rand() % 2) { T(static_cast<T~>(x)); return true; }
>> > return false;
>> > }
>> >
>> > The compiler will still emit the destructor call for x in the caller of maybe_destroy - there's nothing in the interface that communicates whether an object has been destroyed or not, *to the compiler*. Being able to handle this case has been the major stumbling block for pretty much every proposal in this space.
>> >
>> > G
>> >
>> > On Tue, Feb 1, 2022 at 9:26 AM Sébastien Bini via Std-Proposals <std-proposals_at_[hidden]> wrote:
>> >>
>> >> Hello everyone,
>> >>
>> >> I've worked on a proposal in the last months to introduce relocation (a destructive move) in C++. I wrote the proposal (you will find it enclosed with this email) but it's quite large so I'll write a small overview here.
>> >>
>> >> Relocation allows to "move" one object into another one. It is similar to move constructors, except that relocation guarantees that moved objects are destructed right after and cannot be reused.
>> >>
>> >> This proposal is motivated by:
>> >> - the confusing and often not properly implemented "moved-from" state, introduced by move constructor.
>> >> - relocation is simpler to implement than move semantics as the relocated object can be left in a dirty invalid state.
>> >> - const variables cannot be moved with C++ move semantics. But they can "relocated" with this proposal.
>> >>
>> >> This introduces three main additions to the language:
>> >>
>> >> 1. Relocation reference
>> >>
>> >> A new type of reference, called "relocation reference". A relocation reference on T is denoted by T~. This reference is mainly introduced because of the new constructor.
>> >>
>> >> 2. Relocation constructor
>> >>
>> >> We introduce a new constructor, the relocation constructor:
>> >> class T {
>> >> T(T~ other) noexcept;
>> >> };
>> >>
>> >> This constructor has a new feature no other C++ constructor has: it acts as a destructor with regards to its parameter ("other" in the code sample). Hence when this constructor is called, a new instance is built as always, but the instance it was built from is destructed. This means that the destructor of the moved instance must not be called (otherwise the instance would be destructed twice).
>> >>
>> >> This constructor is usually quite straightforward to implement, you need simply to copy all data-members from other into the new instance.
>> >>
>> >> For instance, implementing the relocation constructor for std::unique_ptr is simple. You simply need to copy the internal pointer to the new instance. The moved instance can be left untouched, and the memory it still owns won't be deleted as the moved instance destructor will not be called.
>> >> In fact there is so little to do that the default implementation does the job:
>> >> unique_ptr(unique_ptr~) noexcept = default;
>> >>
>> >> 3. The reloc operator
>> >>
>> >> Lastly, we introduce a new unary operator: reloc. It will usually be used like this: auto y = reloc x;
>> >> This relocates x into y, leaving x in a destructed state.
>> >> This new operator (a) handles the construction of the new instance (will use the relocation constructor, the move constructor or the copy constructor, picked in that order), (b) ensures the destruction of the relocated instance and (c) prevents any further use in the code of the relocated instance.
>> >>
>> >> Consider the following scenario:
>> >> const T x;
>> >> auto y = reloc x;
>> >> // std::cout << x << std::endl;
>> >>
>> >> The second line builds y from x:
>> >> - Case 1: the type of x provides a relocation constructor: The relocation constructor is called. At the end of the expression x is considered destructed because of the relocation constructor. The destructor of x will not be called when its end of scope is reached.
>> >> - Case 2: the type of x does not provide a relocation constructor, but a move or copy constructor: The move constructor is called if it exists (if not the copy constructor is called) to construct y from x. reloc must ensure the destruction of x, so the destructor of x is called at the end of the evaluation of the second line.
>> >>
>> >> The third line attempts to reuse a variable that was relocated. Uncommenting this line will raise a compile error. reloc forbids further mention of a name that resolves to a relocated object.
>> >>
>> >> This is it, thank you for reading :)
>> >>
>> >> Best regards,
>> >> Sébastien Bini
>> >> --
>> >> Std-Proposals mailing list
>> >> Std-Proposals_at_[hidden]
>> >> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>> >
>> > --
>> > Std-Proposals mailing list
>> > Std-Proposals_at_[hidden]
>> > https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
If we say that operator reloc invokes move-constructor and then
'removes' source object from the scope, then by having either:
a) MyType(MyType&&) = bitcopy;
b) MyType(MyType&&) { <user code> }
You will be able to get both optimal code for bitcopyable types, and
any custom behaviour for relocation you might imagine.
And all this without a need for a new special member function, and new
complex rules.
This integrates really well to existing code.
Regards,
Maciej
wt., 1 lut 2022 o 17:10 Sébastien Bini <sebastien.bini_at_[hidden]> napisał(a):
>
> Hello again,
>
> Thank you both for your comments.
>
> @Gašper Ažman :
> > I'd very much like to see sections on the comparisons with the various papers that came in the past. It'll both show you're aware of all the historical context as an author, and save the readers the trouble of having to think through all of it when reviewing.
>
> I will work on it. Thank you for pointing out the proposals on the same topic, I find it hard to search for them. Is there some central place I'm missing? I only found http://www.open-std.org/jtc1/sc22/wg21/docs/papers/ but it's not convenient to browse through all of them. Likewise, you mentioned this section should also "answer to the previously surfaced objections"; but do you know how I find them?
>
> I will also get in touch with Arthur O'Dwyer and Howard Hinnant. I see Arthur O'Dwyer is the author of one of the linked proposals but has Howard Hinnant worked on something similar?
>
> @Maciej Cencora:
> I take it your proposition is to add the reloc keyword to P1029R3. I honestly did not know of P1029R3, sorry about that. I think it's definitely something to consider.
>
> On one hand, I like the new T(T&&) = relocate/bitcopies; move constructor. I get your point, and it involves less changes in the core langage, which is a good thing.
>
> However, having a dedicated constructor for relocation allows for more freedom in the relocation implementation. In what I propose, the relocation constructor can be more than a memcpy, if needs be. I don't know if there are cases where relocation involves more than a memcpy, but it might (instance tracking?). It's something I need to ponder. But it's definitely a good point, thanks!
>
> Regards,
> Sébastien
>
>
> On Tue, Feb 1, 2022 at 12:41 PM Maciej Cencora <m.cencora_at_[hidden]> wrote:
>>
>> Hi,
>>
>> I don't think adding yet another reference type and yet another
>> special member function is a good way to solve this, as the complexity
>> in this area is already big. I think the solution should reuse as much
>> as possible of existing syntax.
>> What I think would be better, is that we kept move-constructors, but
>> we add a new syntax to mark if a class is relocatable:
>> MyClass(MyClass&& other) = relocate;
>>
>> Marking such a constructor as relocate (no user-provided definition
>> allowed), would indicate to compiler two things: 1) moving is just a
>> trivial memcpy, 2) moved-from object is left in a state (e.g. default
>> constructed) where destructor call has no-side effect.
>> So current code:
>> MyClass newObject(std::move(other));
>>
>> becomes (pseudo-code):
>> MyClass newObject = uninitialized;
>> new (&newObject) MyClass(other); // or memcpy
>> new (&other) MyClass();
>> // when 'other' goes out of scope, its destruction can be skipped
>> because it is a noop.
>>
>> So far it does not change anything w.r.t. what we have now.
>> But if we add Sebastian's proposed operator reloc with such a
>> semantics that it will call move-constructor, and mark source object
>> as already destroyed we get the semantic checking that moved-from
>> object cannot be used anymore.
>> Operator reloc can be called on any type that is move-constructible,
>> it is just that for types marked as relocatable such an operation can
>> be better optimized.
>>
>> MyClass newObject = reloc other;
>> // now other cannot be referenced any more
>>
>> Since we do not introduce new types of references, or new types of
>> member functions, we can gradually migrate code from calling std::move
>> to using reloc operator (while preserving ABI and API compatibility).
>> This will also allow to finally have a optimal construction for types
>> with user-defined constructors:
>>
>> struct Person
>> {
>> Person(std::string firstName, lastName)
>> : firstName(reloc firstName)
>> , lastName(reloc lastName)
>> {}
>>
>> std::string firstName, lastName;
>> };
>>
>> Person p1("John", "Doe"); // no temporaries, no move constructors
>> Person p2(p1.firstName, p2.lastName); // one copy, no temporaries, no
>> move constructors
>>
>> Regards,
>> Maciej
>>
>> wt., 1 lut 2022 o 11:04 Gašper Ažman via Std-Proposals
>> <std-proposals_at_[hidden]> napisał(a):
>> >
>> > Hi Sebastien,
>> >
>> > you sure made a pretty long write-up! What I'm missing on the first skim-through is a thorough review of the currently published papers in the space and answers to the previously surfaced objections.
>> >
>> > Some of the papers in this space:
>> > http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1144r5.html
>> > http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4158.pdf
>> > http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1029r3.pdf
>> > http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0023r0.pdf
>> >
>> > I also couldn't find any way that your proposal handles maybe-destroy references. For instance, how do you handle
>> >
>> > bool maybe_destroy(T& x) {
>> > if (rand() % 2) { T(static_cast<T~>(x)); return true; }
>> > return false;
>> > }
>> >
>> > The compiler will still emit the destructor call for x in the caller of maybe_destroy - there's nothing in the interface that communicates whether an object has been destroyed or not, *to the compiler*. Being able to handle this case has been the major stumbling block for pretty much every proposal in this space.
>> >
>> > G
>> >
>> > On Tue, Feb 1, 2022 at 9:26 AM Sébastien Bini via Std-Proposals <std-proposals_at_[hidden]> wrote:
>> >>
>> >> Hello everyone,
>> >>
>> >> I've worked on a proposal in the last months to introduce relocation (a destructive move) in C++. I wrote the proposal (you will find it enclosed with this email) but it's quite large so I'll write a small overview here.
>> >>
>> >> Relocation allows to "move" one object into another one. It is similar to move constructors, except that relocation guarantees that moved objects are destructed right after and cannot be reused.
>> >>
>> >> This proposal is motivated by:
>> >> - the confusing and often not properly implemented "moved-from" state, introduced by move constructor.
>> >> - relocation is simpler to implement than move semantics as the relocated object can be left in a dirty invalid state.
>> >> - const variables cannot be moved with C++ move semantics. But they can "relocated" with this proposal.
>> >>
>> >> This introduces three main additions to the language:
>> >>
>> >> 1. Relocation reference
>> >>
>> >> A new type of reference, called "relocation reference". A relocation reference on T is denoted by T~. This reference is mainly introduced because of the new constructor.
>> >>
>> >> 2. Relocation constructor
>> >>
>> >> We introduce a new constructor, the relocation constructor:
>> >> class T {
>> >> T(T~ other) noexcept;
>> >> };
>> >>
>> >> This constructor has a new feature no other C++ constructor has: it acts as a destructor with regards to its parameter ("other" in the code sample). Hence when this constructor is called, a new instance is built as always, but the instance it was built from is destructed. This means that the destructor of the moved instance must not be called (otherwise the instance would be destructed twice).
>> >>
>> >> This constructor is usually quite straightforward to implement, you need simply to copy all data-members from other into the new instance.
>> >>
>> >> For instance, implementing the relocation constructor for std::unique_ptr is simple. You simply need to copy the internal pointer to the new instance. The moved instance can be left untouched, and the memory it still owns won't be deleted as the moved instance destructor will not be called.
>> >> In fact there is so little to do that the default implementation does the job:
>> >> unique_ptr(unique_ptr~) noexcept = default;
>> >>
>> >> 3. The reloc operator
>> >>
>> >> Lastly, we introduce a new unary operator: reloc. It will usually be used like this: auto y = reloc x;
>> >> This relocates x into y, leaving x in a destructed state.
>> >> This new operator (a) handles the construction of the new instance (will use the relocation constructor, the move constructor or the copy constructor, picked in that order), (b) ensures the destruction of the relocated instance and (c) prevents any further use in the code of the relocated instance.
>> >>
>> >> Consider the following scenario:
>> >> const T x;
>> >> auto y = reloc x;
>> >> // std::cout << x << std::endl;
>> >>
>> >> The second line builds y from x:
>> >> - Case 1: the type of x provides a relocation constructor: The relocation constructor is called. At the end of the expression x is considered destructed because of the relocation constructor. The destructor of x will not be called when its end of scope is reached.
>> >> - Case 2: the type of x does not provide a relocation constructor, but a move or copy constructor: The move constructor is called if it exists (if not the copy constructor is called) to construct y from x. reloc must ensure the destruction of x, so the destructor of x is called at the end of the evaluation of the second line.
>> >>
>> >> The third line attempts to reuse a variable that was relocated. Uncommenting this line will raise a compile error. reloc forbids further mention of a name that resolves to a relocated object.
>> >>
>> >> This is it, thank you for reading :)
>> >>
>> >> Best regards,
>> >> Sébastien Bini
>> >> --
>> >> Std-Proposals mailing list
>> >> Std-Proposals_at_[hidden]
>> >> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>> >
>> > --
>> > Std-Proposals mailing list
>> > Std-Proposals_at_[hidden]
>> > https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
Received on 2022-02-01 17:29:12