This attribute shall be silently ignored if the type does not have a public, non-deleted, constexpr, in-class defined default constructor and if there is not a public, non-deleted, non-virtual move constructor.
If a type T has attribute [[move_relocates]], instead of calling the defined move constructor, the compiler will implement move construction as-if by memcpy(dest, src, sizeof(T)), followed by as-if memcpy(src, &T{}, sizeof(T)).
It is considered good practice that the move constructor be defaulted with an explanatory comment mentioning the [[move_relocates]], as the move constructor is never called on types with non ignored [[move_relocates]].
If STL containers see that for their type T that std::is_relocatable<T> is true, that
std::has_virtual_destructor<T> is false, and if the Allocator they are configured with has
a defaulted construct() and destroy(), they will relocate the storage for type T in the move
construction + destruction cycle by a method equivalent to copying bits, but without calling
the move constructor, nor the destructor on the moved-from storage.
template<class T>
class [[move_relocates]] unique_ptr { ... };
template<class T, class Deleter=std::default_delete<T>>
class [[move_relocates(std::is_relocatable_v<Deleter>)]] unique_ptr { ... };
I'm skeptical about the defaulted move constructor. You're asking the presence of the attribute change what the default move constructor/assignment would be, but the point of the attribute is to be able to avoid invoking the move constructor to begin with and just use memcpy.
This attribute shall be silently ignored if the type does not have a public, non-deleted, constexpr, in-class defined default constructor and if there is not a public, non-deleted, non-virtual move constructor.
I don't like the idea of silently ignoring this attribute. Here, you've specified a specific list of conditions, and if those conditions are not met, then the compiler must ignore the attribute. Since those conditions are well-defined, users can reasonably be expected to know what they are. And therefore if they use the attribute on a type that doesn't fit these conditions, then they've probably made a mistake. And that shouldn't be ignored.
To me, it should be a hard-error to specify such types with the attribute.
Oh, and constructors can't be `virtual` ;)
I'm skeptical about the defaulted move constructor. You're asking the presence of the attribute change what the default move constructor/assignment would be, but the point of the attribute is to be able to avoid invoking the move constructor to begin with and just use memcpy.
Why not tell the compiler to relocate? With a context keyword.
type(type&&) = relocate; // instead of "= default"
void g(std::unique_ptr<int>);
void h(int*); // or std::observer_ptr<int>
void f()
{
std::unique_ptr<int> p { new int{42} };
if (/* condition */)
{
g(std::move(p)); // should it relocate?
}
else
{
h(p.get());
}
// should p be destroyed or not, here?
}
This attribute shall be silently ignored if the type does not have a public, non-deleted, constexpr, in-class defined default constructor and if there is not a public, non-deleted, non-virtual move constructor.
I don't like the idea of silently ignoring this attribute. Here, you've specified a specific list of conditions, and if those conditions are not met, then the compiler must ignore the attribute. Since those conditions are well-defined, users can reasonably be expected to know what they are. And therefore if they use the attribute on a type that doesn't fit these conditions, then they've probably made a mistake. And that shouldn't be ignored.
To me, it should be a hard-error to specify such types with the attribute.
Oh, and constructors can't be `virtual` ;)
If a type T has attribute [[move_relocates]], instead of calling the defined move constructor, the compiler will implement move construction as-if by memcpy(dest, src, sizeof(T)), followed by as-if memcpy(src, &T{}, sizeof(T)).
I find myself concerned with the "will implement" bit, which is also echoed by this statement:It is considered good practice that the move constructor be defaulted with an explanatory comment mentioning the [[move_relocates]], as the move constructor is never called on types with non ignored [[move_relocates]].
The issue with the "will implement" goes back to the nature of attributes in C++.
The [[no_unique_address]] proposal tried to weasel-word its way around the fact that it was expanding the nature of attributes. It did so by making user-visible code behave the same way regardless of the attribute being present or not.
This attribute goes even farther. A defaulted move constructor is not a correct move constructor, so this "good practice" is encouraging people to write code that won't work if the attribute is not implemented. At least with [[no_unqiue_address]], code that is correct with the attribute is still correct without it. But that's not the case here.
I don't feel it is appropriate for attributes to impose such a requirement on code generation. [[no_unique_address]] weasels out of this by making all of its behavior implementation-dependent. Which admittedly is rather silly, when we could have laid down rules for when it's required.
But that's the price you pay when you use an attribute for something that should by all rights be a keyword.
If STL containers see that for their type T that std::is_relocatable<T> is true, that
std::has_virtual_destructor<T> is false, and if the Allocator they are configured with has
a defaulted construct() and destroy(), they will relocate the storage for type T in the move
construction + destruction cycle by a method equivalent to copying bits, but without calling
the move constructor, nor the destructor on the moved-from storage.
Your previous discussion of [[move_relocatable]] states that such types are allowed/required to replace "move construction + destruction" with "copy + copy-default + destruction". So user-code would be restricted to exactly and only that replacement.
But here, you bless "STL containers" with something more: the ability to replace "move construction + destruction" with "copy + drop". Why are only "STL containers" allowed to do that replacement? Shouldn't any applicable user code be allowed to make this substitution?
Also, why "STL containers" in general? I don't see any real benefit for most `std::list` or `std::forward_list` implementations to do this. I don't think `std::deque` benefits from it either, or any of the other node-based containers. Even `basic_string<T>` wouldn't need it, since it already has a restriction that its `T` must be TriviallyCopyable (though the requirement that it must be a "char-like object", which must be a POD, which includes TriviallyCopyable).
This is really just about `vector`.
Overall, I think this a big improvement over the deduction approach. I'd much prefer that this proposal merge with Arthur's: using his standard library infrastructure for doing library-based relocation, coupled with your attribute for declaring types that allow the compiler the freedom to do special things.
That is, I would say that you should focus on the attribute and compiler behavior, and let Arthur focus on the library and memory model behavior.
How would this work for the real unique_ptr which is also templated on its deleter? In that case, we'd want unique_ptr to be move_relocates if D is relocatable. Would that be something like this?
I would also say that for a type to be applicable for this attribute, the copy constructor must either be public or deleted (and probably should be defaulted if it is not deleted).
Il giorno martedì 17 aprile 2018 10:26:33 UTC+2, Avi Kivity ha scritto:
Why not tell the compiler to relocate? With a context keyword.
type(type&&) = relocate; // instead of "= default"
Because I'm still not convinced that the compiler is always able to relocate even in presence of a strong request to do that. Consider this code:
Il giorno martedì 17 aprile 2018 10:26:33 UTC+2, Avi Kivity ha scritto:
Why not tell the compiler to relocate? With a context keyword.
type(type&&) = relocate; // instead of "= default"
Because I'm still not convinced that the compiler is always able to relocate even in presence of a strong request to do that. Consider this code:
void g(std::unique_ptr<int>);
void h(int*); // or std::observer_ptr<int>
void f()
{
std::unique_ptr<int> p { new int{42} };
if (/* condition */)
{
g(std::move(p)); // should it relocate?
}
else
{
h(p.get());
}
// should p be destroyed or not, here?
}
--A.
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/556a287c-505a-4ead-a752-244df90de614%40isocpp.org.
You'd need, at least, base classes and members to be marked [[move_relocates]]. unique_ptr<T, D> doesn't inherit from D, it just has a member D. Likewise, vector<T, A> just has a member A.
But then, the libstdc++ implementation of unique_ptr<T, D> has a base class which has a member of type tuple<T, D>. So in order for that to work, tuple and all of its glorious base classes would all have to be marked [[move_relocates]]... just in case?
You'd need, at least, base classes and members to be marked [[move_relocates]]. unique_ptr<T, D> doesn't inherit from D, it just has a member D. Likewise, vector<T, A> just has a member A.Yes, this is what I added to the current draft of the paper.But then, the libstdc++ implementation of unique_ptr<T, D> has a base class which has a member of type tuple<T, D>. So in order for that to work, tuple and all of its glorious base classes would all have to be marked [[move_relocates]]... just in case?Currently, it is on the implementation to make variants of each inherited class, one with [[move_relocates]], one without, and to choose at compile time appropriately.
On Tue, Apr 17, 2018 at 5:11 AM, Barry Revzin <barry....@gmail.com> wrote:On Tue, Apr 17, 2018 at 5:30 AM Niall Douglas <nialldo...@gmail.com> wrote:How would this work for the real unique_ptr which is also templated on its deleter? In that case, we'd want unique_ptr to be move_relocates if D is relocatable. Would that be something like this?Good point. We would need to require that all base classes are also marked [[move_relocates]], otherwise [[move_relocates]] is ignored. Thank you.NiallYou'd need, at least, base classes and members to be marked [[move_relocates]]. unique_ptr<T, D> doesn't inherit from D, it just has a member D. Likewise, vector<T, A> just has a member A. But then, the libstdc++ implementation of unique_ptr<T, D> has a base class which has a member of type tuple<T, D>. So in order for that to work, tuple and all of its glorious base classes would all have to be marked [[move_relocates]]... just in case?This example comes up in my C++Now talk; I actually use "reallocating a vector<unique_ptr<int>>" as my performance benchmark.My tentative solution (slide 23) is to make std::unique_ptr conditionally trivially relocatable:template<class T, class D = std::default_delete<T>>
class unique_ptr {
public:
using deleter_type = D;
using pointer = std::remove_reference_t<D>::pointer;
static constexpr bool is_trivially_relocatable =
std::is_trivially_relocatable_v<pointer> &&
std::is_trivially_relocatable_v<deleter_type>;
// ...
};It is important to remember that the "relocation operation" is the combination of one "move-construct" operation and one "destroy" operation (on the source of the move). Relocation is not an optimization of move-construct alone. If you start thinking in terms of "replacing move with relocate" you will rapidly confuse yourself. You must think in terms of "replacing move+destroy with relocate." (Slide 25 shows how std::vector uses uninitialized_relocate() as a substitute for uninitialized_move()+destroy() when it detects that the replacement is safe. Slide 18 shows how uninitialized_relocate(), in turn, detects when it is dealing with a contiguous range of trivially relocatable objects, and optimizes into a simple memcpy.)The compiler can help us identify the base cases (slides 21+22), but there is no general way to recursively infer whether a class is intended to be trivially relocatable or not (because you cannot reliably detect semantics by examining syntax; this is the Concepts problem, and the reason for forward_iterator_tag). Since there is no general way for the compiler to infer trivial relocatability, we must have the programmer opt-in in specific cases.Niall wants to use a novel attribute to opt-in to trivial-relocatability; my approach uses a pure-library trait modeled after is_transparent.
In Niall's proposal, the attribute is not just about "trivial relocatability" as your proposal defines it. The attribute allows for more than just memcpy+drop (indeed technically, it doesn't even allow for that). The use of the attribute requires that the compiler will never call the move constructor. Anytime move construction happens, the compiler will replace the constructor call with a pair of `memcpy`-equivalent operations. Coupled with the statement that calling the destructor on a default-constructed value is a no-op, this ensures that user code is never involved in move+destroy operations.
ABIs have the freedom to store TriviallyCopyable types in registers because no user code gets called when they get copied/moved/destroyed. As such, no user code can detect that an object is being copied when the standard says that it isn't to be copied, or not copied when the standard says it must be copied, or destroyed when the standard says it still exists, and so forth. This freedom allows TrivialCopyable types to live in registers if the compiler/ABI so chooses rather than in actual storage.
I think that both proposals are necessary. Solving the problem of allowing more objects in registers is important. But solving the problem of standard library inefficiencies with regard to movable types is also important. And while the solutions are related (if a type can fit in registers, it certainly can do the library relocation thing), they're ultimately different.
On Tue, Apr 17, 2018 at 1:05 PM, Nicol Bolas <jmck...@gmail.com> wrote:In Niall's proposal, the attribute is not just about "trivial relocatability" as your proposal defines it. The attribute allows for more than just memcpy+drop (indeed technically, it doesn't even allow for that). The use of the attribute requires that the compiler will never call the move constructor. Anytime move construction happens, the compiler will replace the constructor call with a pair of `memcpy`-equivalent operations. Coupled with the statement that calling the destructor on a default-constructed value is a no-op, this ensures that user code is never involved in move+destroy operations.It is physically possible for a type to be "trivially relocatable" (that is, move+destroy be equivalent to memcpy+drop) without having the property "destroying a default-constructed object is a no-op." I admit I'm not sure how likely that is in idiomatic C++ code.
A simple example is nn_unique_ptr<T>, which is trivially relocatable but is not default-constructible at all. (There is a sense in which this type has a "default-constructed empty state", but that state is not literally produced by the default constructor.)The other examples I can think of off the top of my head are fairly contrived.
ABIs have the freedom to store TriviallyCopyable types in registers because no user code gets called when they get copied/moved/destroyed. As such, no user code can detect that an object is being copied when the standard says that it isn't to be copied, or not copied when the standard says it must be copied, or destroyed when the standard says it still exists, and so forth. This freedom allows TrivialCopyable types to live in registers if the compiler/ABI so chooses rather than in actual storage.I don't think the "no user code gets called" part is actually what allows TriviallyCopyable types to live in registers. The compiler can make other types live in registers too, if it wants. Here is an example of GCC placing a unique_ptr in a register.int *p(int *q){std::unique_ptr<int> u(q);return u.release();}Objects can "live" in registers whenever the compiler feels like it, with one major exception: At cross-module call boundaries, both sides must agree on where the function parameter object is expected to live! In practice, this means that the location of the parameter must be determined only by the properties of its type, and not by more local properties (such as the escape analysis that permitted GCC to place the local variable u in a register)."Both sides must agree" is just another way of saying "there must be a standard calling convention."The standard calling convention for Linux is defined by the Itanium C++ ABI. The Itanium C++ ABI defines where parameters are passed based on the properties of their types (a wise, but not inevitable, strategy). In particular, Itanium looks at the trivial copyability of the parameter type to decide where it's passed. (And, in more recent/future revisions, the Itanium C++ ABI will also look at whether the type has __attribute__((trivial_abi)), to decide where it's passed.)Nicol, what in your view is the relationship between Niall's attribute proposal and __attribute__((trivial_abi))? Would you describe Niall's proposal as just an ISO-adoption of the Itanium-status-quo, or do you see other important components as well in Niall's current proposal?
void func(observer_ptr<T> pT);
void func(T *pT);
[...]I think that both proposals are necessary. Solving the problem of allowing more objects in registers is important. But solving the problem of standard library inefficiencies with regard to movable types is also important. And while the solutions are related (if a type can fit in registers, it certainly can do the library relocation thing), they're ultimately different.I do not believe that "if a type can fit in registers, it certainly can do the library relocation thing." For example, its move-constructor (by itself) might have side effects that the user is not willing to discard.
Here is a compilable example:Notice that the parameter is passed in %rdi and the result is returned in %rax.Notice that there are no load/store operations happening. We never touch memory.Notice that the compiler quietly eliminates the dead store and puts() from the destructor.Finally, very importantly, notice that the compiler correctly preserves the side-effecting puts() in our type's non-trivial move-constructor.
On Tuesday, April 17, 2018 at 5:01:46 PM UTC-4, Arthur O'Dwyer wrote:On Tue, Apr 17, 2018 at 1:05 PM, Nicol Bolas <jmck...@gmail.com> wrote:In Niall's proposal, the attribute is not just about "trivial relocatability" as your proposal defines it. The attribute allows for more than just memcpy+drop (indeed technically, it doesn't even allow for that). The use of the attribute requires that the compiler will never call the move constructor. Anytime move construction happens, the compiler will replace the constructor call with a pair of `memcpy`-equivalent operations. Coupled with the statement that calling the destructor on a default-constructed value is a no-op, this ensures that user code is never involved in move+destroy operations.It is physically possible for a type to be "trivially relocatable" (that is, move+destroy be equivalent to memcpy+drop) without having the property "destroying a default-constructed object is a no-op." I admit I'm not sure how likely that is in idiomatic C++ code.A simple example is nn_unique_ptr<T>, which is trivially relocatable but is not default-constructible at all. (There is a sense in which this type has a "default-constructed empty state", but that state is not literally produced by the default constructor.)The other examples I can think of off the top of my head are fairly contrived.
Such a type may be relocatable for library purposes, but it is not one for which you can apply the [[move_relocatable]] attribute.
That being said, I think Niall's definition for this promise is overly restrictive. There's no reason why an `nn_unique_ptr` type should be forbidden from being [[move_relocatable]]. A different set of promises can allow it to be move-relocatable.
My promise list would be:
1. The type shall have a publicly-accessible move constructor.
2. The type shall have a publicly-accessible destructor.
3. The writer of the type warrants that the move constructor gives the newly constructed object a value equivalent to performing a `memcpy` from the original state of the moved-from object.
4. The writer of the type warrants that the move constructor puts the moved-from object in a state such that calling the destructor on it has no side effects and does nothing.
The upsides of this to me are:
* expanding the number of types that can be [[move_relocatable]]
* since the move constructor can still be called, you don't have the luxury of `= default`ing it, and thus cannot create inconsistent behavior.
* compilers don't have to have special code to turn `memcpy+memcpy+destroy" into "memcpy+drop"; they can just go straight there.
The only downside I can see is that this is identical to destructive move. And that's terrible... somehow.
ABIs have the freedom to store TriviallyCopyable types in registers [...]
The compiler can make other types live in registers too, if it wants. Here is an example of GCC placing a unique_ptr in a register.int *p(int *q){std::unique_ptr<int> u(q);return u.release();}Objects can "live" in registers whenever the compiler feels like it, with one major exception: At cross-module call boundaries, both sides must agree on where the function parameter object is expected to live! In practice, this means that the location of the parameter must be determined only by the properties of its type, and not by more local properties (such as the escape analysis that permitted GCC to place the local variable u in a register)."Both sides must agree" is just another way of saying "there must be a standard calling convention."The standard calling convention for Linux is defined by the Itanium C++ ABI. The Itanium C++ ABI defines where parameters are passed based on the properties of their types (a wise, but not inevitable, strategy). In particular, Itanium looks at the trivial copyability of the parameter type to decide where it's passed. (And, in more recent/future revisions, the Itanium C++ ABI will also look at whether the type has __attribute__((trivial_abi)), to decide where it's passed.)Nicol, what in your view is the relationship between Niall's attribute proposal and __attribute__((trivial_abi))? Would you describe Niall's proposal as just an ISO-adoption of the Itanium-status-quo, or do you see other important components as well in Niall's current proposal?
Now, I haven't read the entire thread from the link you provided, but it seems to be that [[trivial_abi]] has the substitutability of one type for another as a main feature. That if you have a function that returns a `T*`, you could turn it into an `observer_ptr<T>` without the ABI of the function being considered different. That seems a primary goal of the feature.
[[trivial_abi]] says that a move+destroy operation can be converted into a memcpy+drop (or even less), but it doesn't require it in all cases.
Notice that the parameter is passed in %rdi and the result is returned in %rax.Notice that there are no load/store operations happening. We never touch memory.Notice that the compiler quietly eliminates the dead store and puts() from the destructor.Finally, very importantly, notice that the compiler correctly preserves the side-effecting puts() in our type's non-trivial move-constructor.
What happens if you put the elements of that code in different translation units?
Niall's definition takes "move+destroy", turns it into "memcpy+memcpy+destroy", which the compiler is expected to optimize into "memcpy+drop". My way simply turns "move+destroy" into "memcpy+drop" directly; if the compiler only detects "move" with no "destroy" being visible, then that's what gets called.
The upsides of this to me are:
* expanding the number of types that can be [[move_relocatable]]
* since the move constructor can still be called, you don't have the luxury of `= default`ing it, and thus cannot create inconsistent behavior.
* compilers don't have to have special code to turn `memcpy+memcpy+destroy" into "memcpy+drop"; they can just go straight there.
The only downside I can see is that this is identical to destructive move. And that's terrible... somehow.
On Tue, Apr 17, 2018 at 6:52 PM, Nicol Bolas <jmck...@gmail.com> wrote:On Tuesday, April 17, 2018 at 5:01:46 PM UTC-4, Arthur O'Dwyer wrote:On Tue, Apr 17, 2018 at 1:05 PM, Nicol Bolas <jmck...@gmail.com> wrote:In Niall's proposal, the attribute is not just about "trivial relocatability" as your proposal defines it. The attribute allows for more than just memcpy+drop (indeed technically, it doesn't even allow for that). The use of the attribute requires that the compiler will never call the move constructor. Anytime move construction happens, the compiler will replace the constructor call with a pair of `memcpy`-equivalent operations. Coupled with the statement that calling the destructor on a default-constructed value is a no-op, this ensures that user code is never involved in move+destroy operations.It is physically possible for a type to be "trivially relocatable" (that is, move+destroy be equivalent to memcpy+drop) without having the property "destroying a default-constructed object is a no-op." I admit I'm not sure how likely that is in idiomatic C++ code.A simple example is nn_unique_ptr<T>, which is trivially relocatable but is not default-constructible at all. (There is a sense in which this type has a "default-constructed empty state", but that state is not literally produced by the default constructor.)The other examples I can think of off the top of my head are fairly contrived.
Such a type may be relocatable for library purposes, but it is not one for which you can apply the [[move_relocatable]] attribute.Right. Niall's attribute doesn't work for nn_unique_ptr<T>, but...That being said, I think Niall's definition for this promise is overly restrictive. There's no reason why an `nn_unique_ptr` type should be forbidden from being [[move_relocatable]]. A different set of promises can allow it to be move-relocatable.Right.My promise list would be:
1. The type shall have a publicly-accessible move constructor.
2. The type shall have a publicly-accessible destructor.
3. The writer of the type warrants that the move constructor gives the newly constructed object a value equivalent to performing a `memcpy` from the original state of the moved-from object.
4. The writer of the type warrants that the move constructor puts the moved-from object in a state such that calling the destructor on it has no side effects and does nothing.My promise list, as to-be-presented on May 8, is even simpler:1. The type shall have an accessible move constructor. (Not necessarily public, but that's a super nitpick.)2. The type shall have an accessible destructor.3. The writer of the type warrants that the move constructor followed by the destructor (of the source) produces results semantically equivalent to "memcpy+drop".I don't bother to add silly restrictions about the allowable state of the moved-from object in the instant between move-from and destruction.
Adding those arbitrary restrictions gains you nothing, and costs you the ability to relocate types such as std::list<T> (assuming one of those old implementations where default-constructing a std::list<T> allocates a sentinel node).
The upsides of this to me are:
* expanding the number of types that can be [[move_relocatable]]
* since the move constructor can still be called, you don't have the luxury of `= default`ing it, and thus cannot create inconsistent behavior.
* compilers don't have to have special code to turn `memcpy+memcpy+destroy" into "memcpy+drop"; they can just go straight there.
The only downside I can see is that this is identical to destructive move. And that's terrible... somehow.All correct. :) Except that these ideas are significant different from the original old-school "destructive move" ideas; the major point in favor of the library approach I propose is that it is not only implementable but implemented (in Qt, EASTL, BSL, etc).ABIs have the freedom to store TriviallyCopyable types in registers [...]The compiler can make other types live in registers too, if it wants. Here is an example of GCC placing a unique_ptr in a register.int *p(int *q){std::unique_ptr<int> u(q);return u.release();}Objects can "live" in registers whenever the compiler feels like it, with one major exception: At cross-module call boundaries, both sides must agree on where the function parameter object is expected to live! In practice, this means that the location of the parameter must be determined only by the properties of its type, and not by more local properties (such as the escape analysis that permitted GCC to place the local variable u in a register)."Both sides must agree" is just another way of saying "there must be a standard calling convention."The standard calling convention for Linux is defined by the Itanium C++ ABI. The Itanium C++ ABI defines where parameters are passed based on the properties of their types (a wise, but not inevitable, strategy). In particular, Itanium looks at the trivial copyability of the parameter type to decide where it's passed. (And, in more recent/future revisions, the Itanium C++ ABI will also look at whether the type has __attribute__((trivial_abi)), to decide where it's passed.)Nicol, what in your view is the relationship between Niall's attribute proposal and __attribute__((trivial_abi))? Would you describe Niall's proposal as just an ISO-adoption of the Itanium-status-quo, or do you see other important components as well in Niall's current proposal?
Now, I haven't read the entire thread from the link you provided, but it seems to be that [[trivial_abi]] has the substitutability of one type for another as a main feature. That if you have a function that returns a `T*`, you could turn it into an `observer_ptr<T>` without the ABI of the function being considered different. That seems a primary goal of the feature.No, that's incorrect.
This is not abstract — this is literally implemented in Clang. You can go play with it on Godbolt and see what's true and what's false.
On 18 April 2018 at 11:31, Niall Douglas <nialldo...@gmail.com> wrote:
> Nobody is saying it is anything but a stopgap measure. In particular, not a
> single person at the conferences could remember exactly what the problem
> with destructive moves was or is, only that "it's bad" for some reason. And
> we've got very senior folk who I talked to about this unable to remember the
> precise details of why it's a bad thing, Roger Orr, Alasdair Meridith,
> Richard Smith and so on.
I, however, have no trouble remembering what that problem is. The problem is
that the previous proposals suggested that it would be possible to end
the lifetime
of an object with a non-trivial destructor without calling that destructor.
That breaks allocators that track lifetimes,
and that breaks all sorts
of assumptions by
programmers and their programs; the usual suggested solution is an
additional opt-in trait,
but then it becomes a question what the supposedly strong motivation
for a destructive move is.
And at that point the proposal authors have backed away, saying "I'm
no longer sure I have
such a strong motivation".
Despite your proposal solving the issue with skipping a destructor and
that it doesn't require
magical trait opt-in, the motivation question still stands.
> we've got very senior folk who I talked to about this unable to remember the
> precise details of why it's a bad thing, Roger Orr, Alasdair Meridith,
> Richard Smith and so on.
I, however, have no trouble remembering what that problem is. The problem is
that the previous proposals suggested that it would be possible to end
the lifetime
of an object with a non-trivial destructor without calling that destructor.
That breaks allocators that track lifetimes, and that breaks all sorts
of assumptions by
programmers and their programs;
the usual suggested solution is an
additional opt-in trait,
but then it becomes a question what the supposedly strong motivation
for a destructive move is.
And at that point the proposal authors have backed away, saying "I'm
no longer sure I have
such a strong motivation".
Despite your proposal solving the issue with skipping a destructor and
that it doesn't require
magical trait opt-in, the motivation question still stands.
The prime motivation for Niall's proposal is not really stated in the proposal itself (and it kind of needs to be), but it's been tossed around in commentary on the SG14 mailing list.
> we've got very senior folk who I talked to about this unable to remember the
> precise details of why it's a bad thing, Roger Orr, Alasdair Meridith,
> Richard Smith and so on.
I, however, have no trouble remembering what that problem is. The problem is
that the previous proposals suggested that it would be possible to end
the lifetime
of an object with a non-trivial destructor without calling that destructor.They did raise this issue, and hence why in my proposal we don't mess with move semantics. The destructor is still called on the moved-from object, same as now.That breaks allocators that track lifetimes, and that breaks all sorts
of assumptions by
programmers and their programs;As a language only proposal which does not modify the lifetime model at all, this sort of code is unaffected by my proposal.the usual suggested solution is an
additional opt-in trait,
but then it becomes a question what the supposedly strong motivation
for a destructive move is.
And at that point the proposal authors have backed away, saying "I'm
no longer sure I have
such a strong motivation".The motivation for my paper is that at least one of the senior committee leadership will not greenlight Herb's deterministic exceptions proposals unless his proposed std::error object can transport a std::exception_ptr within itself. My current reference std::error object stuffs the exception_ptr into a threadsafe global list, and tracks it by handle in order to keep my current reference std::error object trivially copyable. We need additional language support to make std::exception_ptr register storable, then it can be stored directly within the std::error object.
> we've got very senior folk who I talked to about this unable to remember the
> precise details of why it's a bad thing, Roger Orr, Alasdair Meridith,
> Richard Smith and so on.
I, however, have no trouble remembering what that problem is. The problem is
that the previous proposals suggested that it would be possible to end
the lifetime
of an object with a non-trivial destructor without calling that destructor.They did raise this issue, and hence why in my proposal we don't mess with move semantics. The destructor is still called on the moved-from object, same as now.
Here's the thing. Move-Relocation as a language feature doesn't offer much performance improvement.
The thing you have to provide motivation for is not relocation as a concept, but move-relocation as a language feature. And I'd say the most compelling motivation for any language feature is to point to something important that you flat-out cannot do or is really inconvenient which your language feature solves. Rvalue references allow you to distinguish between "reference I can steal resources from" and "reference I can't steal from". `operator<=>` takes something really inconvenient and makes it trivial. Concepts allow us to stop using Byzantine `std::enable_if/void_t` gymnastics. Coroutines allows us to write asynchronous code that looks synchronous. And so on.
Few are the language features whose only motivation is "allows compiler optimizations". Oh sure, there have been some features of that sort. But they're few and far between, and the optimizations they allow are really significant.
So what you're looking for are places in the standard that are restricted to TriviallyCopyable types, but could be expanded to include MoveRelocatable types. I don't think `std::atomic<T>` would apply; its restrictions to TriviallyCopyable types don't really have to do with move construction+destruct cycles. And while you might be able to justify allowing MoveRelocatable types in `basic_string`, I don't think anyone would actually care.
Thus far, I cannot think of any such place in the standard at the present time. Herb's proposal is one of the few language features that actually restricts the acceptable types to TriviallyCopyable ones.
The motivation for my paper is that at least one of the senior committee leadership will not greenlight Herb's deterministic exceptions proposals unless his proposed std::error object can transport a std::exception_ptr within itself.
The motivation for my paper is that at least one of the senior committee leadership will not greenlight Herb's deterministic exceptions proposals unless his proposed std::error object can transport a std::exception_ptr within itself.
Where I could find this proposal? Fast googling do not show anything and last mailing list do not had it.
Niall
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/2765b2e1-6e59-4734-bb1d-100e326b4922%40isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CABPJVnSqGXaefcPNqer_xsR9AMYDYr-SPd57qHKqVQOH8COX5g%40mail.gmail.com.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAFk2RUZYTbnc%3DX2E0Z3Y2Jz0-sU1xT53CBkENkK1vKHrLHEFVQ%40mail.gmail.com.
SG14 members only so far. It may appear at Rapperswil, it may not.Curious: does this mean that I am not an SG14 member?
Does this mean that SG14 suddenly has some special visibility rules for its proposals? If so, then it's extremely unhelpful, especially if we start having proposals outside of it, motivated by proposals *inside*.
I would at least like to get confirmation that the paper number is D0709R12 (Ben mentioned this number in a thread but I didn't see anything in the minutes) and for people to use the paper number when discussing it. That way, at least I know it's a conversion to which I cannot contribute an informed opinion.
--Niall
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/8860d1fe-c3c7-4eca-bb45-fadc0cef218e%40isocpp.org.
It is considered good practice that the move constructor be implemented to cause the exact
same effects as [[move_relocates]] i.e. copying the bits of source to destination followed
by copying the bits of a default constructed instance to source.
struct Foo
{ unique_ptr<Bar> p;
int i;
};
On 2018-04-17 10:56, Alberto Barbati wrote:
BTW, since all this is about the move constructor, wouldn't it be better to put the attribute on the move constructor itself? For example:
type& operator(type&&) [[can_relocate]] { /* definition in case the compiler doesn't relocate */ }
if the condition to apply relocation are met (these conditions includes all considerations about the other constructors), the body of the move constructor is disregarded, the move is performed as-if by memcpy and the move source is not destroyed. If the conditions for relocation are not met or if the compiler decides to ignore the attribute, a valid implemenation of the move constructor is still available and can be used to provide the correct observable behaviour.
Why not tell the compiler to relocate? With a context keyword.
type(type&&) = relocate; // instead of "= default"
type(type &&) noexcept [[bitwise_relocate]];