On 2015–08–02, at 1:05 PM, denis bider <isocp...@denisbider.com> wrote:The problem I see with this is that although it could be "like" memcpy, it is not in fact memcpy.
As far as I can tell, this prevents use of realloc.
But I have another problem with this - which is that it seems to prevent, or make harder, introduction of the feature I really want. What I really want is a standardized rule that would allow the developer to rely on the compiler to automatically mark types is_location_agnostic or is_relocatable. In my opinion, the compiler should do the deduction, and the developer should check the result. This way, the compiler and the developer make each other stronger.
The other way around cannot be done: if we require the developer to do the deduction, the compiler can't check the result.To me, this is the core feature for which I want language support. If I can't rely on the compiler to help me correctly mark my types, then to me, there isn't a major difference to having a standardized property, as opposed to rolling my own. In either case, I won't use it, because it's too dangerous. I don't trust myself to not introduce errors if I'm required to manually mark every type.
On 2015–08–02, at 3:28 PM, denis bider <isocp...@denisbider.com> wrote:
> The programmer marks pointers as relocatable or (potentially) not,> then the compiler summarizes those attributes, and the> programmer uses the summary to decide whether> or not to call realloc? No thanks.What? I can't imagine how you thought that could be what I meant.
This is the feature I need.If I implement my own basic types that are relocatable or location agnostic,
I need the compiler to infer that my other types, which:
- derive from or aggregate my basic types, and- have an implicit or defaulted copy constructor,are also relocatable (or location agnostic).That's the feature I need.
I really don't give a rat's rear end if this feature is not made available. If I cannot count on the compiler's support, then any other "support" for this means nothing to me. Zero.
On 2015–08–02, at 4:20 PM, denis bider <isocp...@denisbider.com> wrote:This is on topic for this thread, because your proposal attempts to fill this niche in a fairly horrendous way, which would prevent the definition of a proper trait with compiler support for inferring it.
On 2015–08–02, at 4:20 PM, denis bider <isocp...@denisbider.com> wrote:This is on topic for this thread, because your proposal attempts to fill this niche in a fairly horrendous way, which would prevent the definition of a proper trait with compiler support for inferring it.The binary is_trivially_destructive_movable_v would be approximated well enough by is_trivially_movable_v< destructive_move_base_t< T > > && is_trivially_destructible_v< destructive_move_base_t< T > >. No language extension needed.
> For that, we need something that may have non-zero false
> negatives, but it cannot have any false positive, for any
> reason. Can you guarantee that the compiler would
> never think a type is relocatable when it really isn't?
On 2015–08–02, at 6:44 PM, denis bider <isocp...@denisbider.com> wrote:In practical observation on x64 Windows, memcpy is 2 - 9 times faster than a loop of placement new followed by destructor.
The greatest speedup - factor of 5-9 - is with commonly used container sizes of 100 - 1000 objects.> nothing is stopping the compiler from detecting that> it’s nothing but a sequence of memcpy callsNothing is stopping the compiler, except for that it isn't yet a fully evolved AI,
and relies on humans to craft optimization rules that fail about as often as they succeed, subject to quirky conditions; and the developer has no way of knowing which is going to happen in advance.One of the main advantages of C++ is that it does not separate the developer from the platform, and does not require trusting complex infrastructure to either optimize code, or fail to.
template <typename T, typename B = typename destructive_move_base_t<B> > T* holy_blessed_relocate(void* d, T* s, size_t n) { T* dt { (T*) d }; for (size_t i=0; i!=n; ++i) { new (dt+i) T(std::move(s[i])); s[i].~T(); } return dt; }
template <typename T, enable_if_t<is_relocatable_v<T>, int>=0> T* holy_blessed_relocate(void* d, T* s, size_t n) { memcpy(d, s, n*sizeof(T)); return (T*) d; } template <typename T, enable_if_t<!is_relocatable_v<T>, int>=0> T* holy_blessed_relocate(void* d, T* s, size_t n) { T* dt { (T*) d }; for (size_t i=0; i!=n; ++i) { new (dt+i) T(std::move(s[i])); s[i].~T(); } return dt; }
On 2015–08–03, at 2:58 AM, denis bider <isocp...@denisbider.com> wrote:
What you're doing is forcing all objects that want to implement relocation to split their data members into a base away from the code, and then to nominate that base for destructive moving.
You are doing this to shoehorn support for relocation, while preserving a conceptual language deficiency in place.
You're doing this to work around the status quo, but while you do so, you don't even benefit from not having to change the language. You're still having to change the language, in order to allow slicing to work in the first place.
You are doing all of this, for the sole and only purpose of having this:
T* holy_blessed_relocate(void* d, T* s, size_t n) {
These lines of code is the whole difference your slicing approach boils down to.
In exchange for this, you are asking everyone in the world to pay for your "conceptual beauty" - such as it is - by forcing them to separate their relocatable implementations into two separate objects.
This is a violation of common sense, a humiliation for C++, and proof that the language is being run by people out of touch with the world.
Whether we call holy_blessed_relocate<T> implemented one way, or the other way, static analysis tools continue to work exactly the same. The only difference is a trivial implementation detail. And the cost you're paying for this detail is to make all the rest of the code ugly.
--
---
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.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
struct A { int i; };
struct B : A { ~B() {} B~(B const&) = default; };
struct C { B m; int* p = &m.A::i; C~(C const& rhs) : m~{rhs.m}, p{&m.A::i} {} };
void f() {
char* storage = new char[sizeof C];
C* p = new (storage) C;
C c ~ *p; // destructive move construction; ends lifetime of *p
delete[] storage;
}
new (to) T(__COOKIE__);
for_each_base<T>([&](auto base_ptr) { uninitialized_destructive_move(&from->*base_ptr, &to->*base_ptr); });
for_each_member<T>([&](auto mem_ptr) { uninitialized_destructive_move(&from->*mem_ptr, &to->*mem_ptr); });
from->~T(__COOKIE__);
On 2015–08–03, at 4:56 PM, David Rodríguez Ibeas <dib...@ieee.org> wrote:There's a couple of things I dislike of David Krauss' proposal, while I understand that it is an attempt to make something work in a much simpler way than the alternatives in the language.
I dislike forcing the design of the types into a base with members and a derived type with functionality on purely theoretical grounds (was this the only concern I may swallow those), but in forcing a particular layout of the members and code I feel it inhibits non-trivial uses. I don't see, for example, how composibility could be obtained. In the libraries that offer relocability through traits this is easily handled in BSL:
I believe that from a user's point of view (more people will develop code than the standard), the simplest approach is what current libraries do: the user that creates a type need only provide the trait, the library does the rest of the work and that work is minimal at the library level. Requiring that the movable type is split hierarchically in a particular way forces a redesign on user types that I don't quite like.
David's approach here is the one that makes this available in the shorter term (the impact on the standard is small enough that reasoning about it is simple and, I think we can agree, safe). But at the same time it is the most expensive alternative of the three for developers, and not trivial to use (not only for the library implementor, but also for the implementor of the relocatable types).
If the other approaches hit a wall and cannot proceed, David's approach is better than nothing, but I'd rather get something less intrusive on user code.
On 2015–08–04, at 11:44 PM, Pablo Halpern <phal...@halpernwightsoftware.com> wrote:
David, I'm afraid I don't understand your base-class destructor idea. Possibly I missed the first part of the discussion. I will point out that, for destructive move, the destructor is actually the easy part. If I understand the language correctly, it is already valid to end the lifetime of an object simply by reusing the memory.
Therefore, a noop destruction should be implementable this way:
template <class T>
void unbless(T& x) noexcept {
struct empty { };
empty *e = ::new (addressof(x)) empty;
e->~empty();
}
If this is invoked in a tight loop, can the compiler's optimizer convert it to a memcpy? If so, then perhaps the trivial variant isn't needed,
though it would be extremely convenient for a large category of types.
The normative permission to end an object’s lifetime without calling its nontrivial destructor §3.8/4 has a stern warning about undefined behavior. The standard library should avoid skipping nontrivial destructors.
Even more convenient would be getting an an implicit definition to do it for you, regardless of triviality. But, that sticks something like destructive_move_ref into the core language.
template <typename T, typename A = allocator<T>>
class list {
// ...
template <typename U = A, typename _ = typename enable_if<is_move_destructible<U>>::type>
~list(void *dst) = default
Best,
Ion
El 06/08/2015 a las 10:35, David Rodríguez Ibeas escribió:
I wasn't as much concerned with the destruction as the construction. The
question comes back again as of whether a compiler could be smart enough
to merge multiple 'memcpy', and while in the trivial case it would seem
reasonable, I fear the are some cases that are not so trivial in the
presence of padding, for example.
I don't think the compiler will merge it, at least many compilers don't optimize even simple loops into memcpy-s:
http://nadeausoftware.com/articles/2012/05/c_c_tip_how_copy_memory_quickly
Of course in practice this kind of destructive move semantics work, as used in BDE, Folly and other libraries:
https://github.com/facebook/folly/blob/master/folly/docs/FBVector.md
"Only a tiny minority of objects are genuinely non-relocatable"
That's several years out of date; modern compilers (gcc 5; clang 3.7; icc) have no problem ignoring padding and optimizing to memcpy. Example: http://goo.gl/a0Xka7
On 2015–08–06, at 7:18 AM, Ion Gaztañaga <igazt...@gmail.com> wrote:Sorry for jumping in the middle of the discussion, I don't think the standard requires calling the destructor at all. The standard states that the lifetime of an object ends when the storage is reused:
class StringType { ~StringType(StringType& x) { // x is already a bitwise copy of *this // perform patch-up of self-references } };
StringType* StringType::~StringType(void* d, StringType* s, std::size_t n=1);
memmove(d, s, n * sizeof(StringType)); StringType* dt = (StringType*) d; for (std::size_t i=0; i!=n; ++i) s[i].~StringType(dt[i]); return dt;
StringType* dest = StringType::~StringType(destptr, src, n);
StringType* StringType::~StringType(void* d, StringType* s, std::size_t n=1);
> *Something* has to bless the new object as created, and the old> object as destroyed (that's the whole reason we're having so> much discussion).See my last message for that:StringType* StringType::~StringType(void* d, StringType* s, std::size_t n=1);This would perform the destructive move of one or more objects as a transaction, just like invoking keywords new or delete is a transaction.It makes most sense to me that the implementation of this transactional move concept performs the equivalent of memcpy for an object before calling the move destructor. It makes things a lot simpler.
> What if the move destructor is called with an object reference that has
> ALREADY had memcpy performed, and is expected to simply perform a patch-up?
This is not allowed. You must not use memcpy to relocate an object
unless its cting-dtor is trivial. If it isn't, you MUST actually call
the cting-dtor, and are not allowed to do anything outside of the same
to create or destroy the new/old objects.
> If the move destructor is declared deleted, the object cannot be
> destructively moved.
My knee-jerk reaction is to forbid that, as the implicit cting-dtor is
possible for any type with a move and/or copy ctor and an accessible
dtor. Deleting the cting-dtor would imply that the type lacks one or
more of those.
--
Matthew
--
---
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/f1zaOjyUem0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
--
Matthew
struct A : B { A(A~& x) : B(x), c(std::xref(x.c)), i(x.i) {} C c; int i; };
A(A~& x) : B(x), c(std::xref(x.c)), i(x.i) {}
A(A~& x) : B(std::xref(x)), c(std::xref(x.c)), i(x.i) {}
--
---
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.
Very nice, but I have one question. How do you handle member variables that might or might not be self-references? For instance, in your example, if the variable a is guaranteed to be either equal to this or a pointer to an object allocated with new, how would you write the relocator function? Most other members of class A could have code like
if (a == this) {
//do something
} else {
//do something else
}
but in the case of the relocator function, by the time we get to the a variable half the object has been relocated and it's not at all obvious what needs to be tested.
Joe Gottman
Very nice, but I have one question. How do you handle member variables that might or might not be self-references?
I still assert that if you *have* a user-defined copy/move ctor or dtor,
you must opt in to the cting-dtor doing other than copy/move plus
destroy of your class. Do we agree on that point?
Anyway, my intent was that one would not declare an explicitly
"defaulted" (or whatever ends up being used) cting-dtor unless the type
already has a non-default copy/move ctor and/or non-default regular
dtor. (And in that case, if you change the class without noticing the
cting-dtor, you are up a creek with either of our approaches :-).)
> If the implicit behavior is OTOH dependent on the presence/absence of
> the other special functions [...]
I never said it wasn't :-). Even with my previous suggestion, I said
that if the members' cting-dtors are all trivial, the aggregate's is
also trivial. The only case we disagreed on was when some member has a
non-trivial cting-dtor *but* the class has default copy/move ctor and
default regular dtor.
(So in either case, anything that e.g. looks like a C struct would
become trivially relocatable.)
> If construction order is indeed more appropriate (which I
> think you are correct on), doesn’t it speak that this is
> more naturally thought of as a destructive move constructor?Yes, I think you're very right!I have updated the proposal with an annex that presents an alternate constructor-like syntax, and discusses the merits and demerits of the two approaches:It seems to me that the constructor-like approach may in fact be superior; no more complex, yet more intuitive; and potentially more extensible, if needed.If folks like this, I'm leaning to promote constructor-like syntax from the annex, and place it in the center of the proposal.> distinction between the multiple & single relocators?Yes - it's just a formal distinction to emphasize cost-less optimization of the single-object case. I have added to the text to make this clearer.
> The static keyword preceding the function A::~A> seems out-of-place no?It is illegal in the definition, yes. It's okay in the declaration.
I have changed the definition to put "static" into C-style comments. I'm still keeping it, to make it clear that the wrappers are invoked as static members.
> it sounds like this is trying to solve the memcpy problem> but it seems like it solves both that & realloc, no?To properly solve the realloc problem, it would be necessary to have the equivalent of realloc_in_place.If realloc_in_place or equivalent fails:- The equivalent of malloc needs to be used to allocate new memory, while keeping old memory.- The static relocator needs to be called to move objects from old location to new. This could be as trivial as memcpy, or may require fix-ups.- The old memory is freed.In the absence of realloc_in_place; and for trivially relocatable objects only; it would work for a container to call realloc, also.To permit use with realloc with non-trivially relocatable objects, the relocator would have to:- use constructor-like syntax (the old location is gone when it is called);- accept a ptrdiff_t instead of reference to the old location.The ptrdiff_t would be the offset between the old location and the new one, and would allow the relocator to perform any patch-ups.The drawback of this approach is that, if you have a small object; e.g. libc++ std::string, which is just one pointer in size; and this object requires a patch-up during relocation; it's twice as costly to execute realloc + patch-up, than to execute a relocator that performs the copy as part of its process.
Basically, the lack of a realloc_in_place or equivalent is the deficiency. A feature such as relocation should not be designed around a library deficiency that should relatively trivial to solve.
struct A {
std::string q, r, s;
>>A(A& x) : >>r(x.r), >>s(x.r) { /* oops */ }
};
template<class T>
struct relocate_wrapper {
template<class... Args> relocate_wrapper(Args&&... args) {
new (&buf) T(std::forward<Args>(args)...);
}
~relocate_wrapper() { reinterpret_cast<T&>(buf).~T(); }
>>relocate_wrapper(relocate_wrapper& rhs) {
new (&buf) T(reinterpret_cast<T&&>(rhs.buf));
reinterpret_cast<T&>(rhs.buf).~T();
}
std::aligned_storage_t<sizeof(T), alignof(T)> buf;
};template <class T>
bool ReAllocInPlace (T* p, size_t n) noexcept
{ return HeapReAlloc(s_processHeap, HEAP_REALLOC_IN_PLACE_ONLY, p, n*sizeof(T)) != nullptr; }
>>A(A& x) : >>B(x), >>c(x.c), >>i(x.i)
{ a = this; }
>>A(A& x) : >>B, >>c, >>i
{ a = this; }
>>A(A& x) : B(std::move(x)), >>c(x.c), >>i(x.i) { a = this; }
struct A { virtual ~A() { cout << "~A()" << endl; } };
struct B : A { virtual ~B() { cout << "~B()" << endl; } };
struct C : B { virtual ~C() { cout << "~C()" << endl; } };
void DestroySubobject(B* b) { b->B::~B(); }
>>A(A& x) : B(std::move(x)), >>c(x.c), >>i(x.i) { x.B::~B(); a = this; }
struct A { int i; int& r = i; >>A(A& x) : >>i, r(i) {} };
--
---
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/f1zaOjyUem0/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
...
>>A(A&& x) : >>B(std::move(x)), >>c(std::move(x.c)) {}
Does anyone know what happened to this proposal and destructive move in general? It seems the current [[trivially relocatable]] covers similar ground, but it is not the same - it can't, well, move-destruct (AFAICC).Is destructive move dead now, in favor of just marking the types, already "trivially relocatable"?