Requirements for "trivially copyable" too strong?

2,200 views
Skip to first unread message

Matheus Izvekov

unread,
Sep 14, 2014, 6:02:09 PM9/14/14
to std-dis...@isocpp.org
Suppose the following incomplete, unoptimized wrapper for objects with potentially unaligned storage:


#include <type_traits>
#include <cstring>

template<class T> struct unaligned {
   
using type = T;
//    static_assert(std::is_trivially_copyable<type>{}, "");
   
template<typename... Args> unaligned(Args&&... args) {
       
const type tmp(std::forward<Args>(args)...);
        memcpy
(&base, &tmp, sizeof(type));
   
}
    unaligned
& operator =(const unaligned &a) {
        memcpy
(&base, &a.base, sizeof(type));
       
return *this;
   
}
   
operator type() const {
        type ret
;
        memcpy
(&ret, &base, sizeof(type));
       
return ret;
   
}
private:
    type
base;
};

struct foo {
   
char a;
    unaligned
<int> b;
} __attribute__((packed));

struct bar {
   
short a;
    unaligned
<int> b;
    unaligned
<unaligned<int>> c;
    unaligned
<unaligned<unaligned<int>>> d;
    unaligned
<unaligned<int>*> e;
} __attribute__((packed));

//static_assert(std::is_trivially_copyable<unaligned<int>>{}, "");
//static_assert(std::is_trivially_copyable<foo>{}, "");
//static_assert(std::is_trivially_copyable<bar>{}, "");

static foo foo_1;
static bar bar_1;

int test() {
    foo_1
.b = 1;
   
int c = foo_1.b;
    bar_1
.b = foo_1.b;
   
int d = bar_1.b;
    bar_1
.c = 2;
    bar_1
.d = 3;
    bar_1
.e = &bar_1.b;
   
*bar_1.e = 3;
   
return c + d;
}

It seems something like this should be supported by the standard, but std::is_trivially_copyable is too stringent to make this useful.
Specifically, if T is trivially copyable, then unaligned<T> should be as well.

The problem is that among the requirements for std::is_trivially_copyable, there are:

1. Has no non-trivial copy constructors (this also requires no virtual functions or virtual bases)

2. Has no non-trivial move constructors

3. Has no non-trivial copy assignment operators

4. Has no non-trivial move assignment operators

5. Has a trivial destructor

It seems like 1, 2, 3 and 4 are too much in this situation.
Or from another point of view, it seems like the set of classes that can be safely copied by std::memcpy is more broad than those that are trivially copyable.

What would be your opinion on changing the definition of is_trivially_copyable to the following requirements:

1.0) A trivially_copyable type must be either:
1.1) A scalar type.
1.2) An array of is_trivially_copyable types.
1.3) A class that contains only is_trivially_copyable members and bases, has no virtual functions or virtual bases, and has a trivial destructor.

2.0) A trivially_copyable type must not be volatile qualified.

That seems enough, but are there any other requirements I am missing?
What are the potential pitfalls of this approach?

sean.mid...@gmail.com

unread,
Sep 14, 2014, 6:52:37 PM9/14/14
to std-dis...@isocpp.org
Another option rather than changing the rules is just to explicitly state that user-defined specializations of std::is_trivially_* are allowed when the user knows best. I don't know if that's legal now but it seems the simplest solution.

Myriachan

unread,
Sep 14, 2014, 7:06:40 PM9/14/14
to std-dis...@isocpp.org
Trivially-copiable isn't just for memcpy layout consistency; it's also a marker indicating that the class does not do anything that would break if the object were moved without a constructor. Just because the type has a trivial destructor doesn't mean that it is safe to copy them around arbitrarily. For example, what if one of the members of the class has a pointer to one of the member elements?

std::vector and such use std::is_trivially_copyable to determine whether to use std::memcpy or std::memmove rather than use standard copy constructors, move constructors, placement operator new and direct destructor invocation. If you have a class that has a constructor that initializes its members to have pointers into itself, but that class doesn't have a destructor because it doesn't have any resources to free, std::vector would cause that class to blow up on a resize operation.

I think that there ought to be a distinction between "implementation-compatible with memcpy" and "trivially-copyable". The former is what you are alluding to. If a non-trivially-copiable class that otherwise meets the requirements you mentioned decides that it is OK *at a certain moment*, it ought to be able to memcpy itself. Right now, though it will work in the majority of implementations if you adhere to those requirements, it is technically undefined behavior to memcpy a non-trivially-copyable class.

Another thread of mine proposed adding a [[trivially_copyable]] attribute to allow a class with a copy constructor to declare that despite that copy constructor, the compiler and classes like std::vector could use std::memcpy if they so chose. The proposal was received poorly.

Melissa

Matheus Izvekov

unread,
Sep 14, 2014, 7:43:25 PM9/14/14
to std-dis...@isocpp.org
On Sunday, September 14, 2014 7:52:37 PM UTC-3, sean.mid...@gmail.com wrote:
> Another option rather than changing the rules is just to explicitly state that user-defined specializations of std::is_trivially_* are allowed when the user knows best. I don't know if that's legal now but it seems the simplest solution.

I don't think such specializations are legal, or that there is precedent for them.


On Sunday, September 14, 2014 8:06:40 PM UTC-3, Myriachan wrote:
Trivially-copiable isn't just for memcpy layout consistency; it's also a marker indicating that the class does not do anything that would break if the object were moved without a constructor.  Just because the type has a trivial destructor doesn't mean that it is safe to copy them around arbitrarily.  For example, what if one of the members of the class has a pointer to one of the member elements?

std::vector and such use std::is_trivially_copyable to determine whether to use std::memcpy or std::memmove rather than use standard copy constructors, move constructors, placement operator new and direct destructor invocation.  If you have a class that has a constructor that initializes its members to have pointers into itself, but that class doesn't have a destructor because it doesn't have any resources to free, std::vector would cause that class to blow up on a resize operation.

I think that there ought to be a distinction between "implementation-compatible with memcpy" and "trivially-copyable".  The former is what you are alluding to.  If a non-trivially-copiable class that otherwise meets the requirements you mentioned decides that it is OK *at a certain moment*, it ought to be able to memcpy itself.  Right now, though it will work in the majority of implementations if you adhere to those requirements, it is technically undefined behavior to memcpy a non-trivially-copyable class.

I see, I was missing that use case for trivially_copyable, thanks.
 

Another thread of mine proposed adding a [[trivially_copyable]] attribute to allow a class with a copy constructor to declare that despite that copy constructor, the compiler and classes like std::vector could use std::memcpy if they so chose.  The proposal was received poorly.

I see. The status quo looks kind of backwards though, it seems that classes that otherwise meet the requirements I mentioned should be the ones that advertise that they are not trivially_copyable, not the other way around.

Do you have a link to your other thread?

Thanks.
 

Sean Middleditch

unread,
Sep 14, 2014, 8:46:37 PM9/14/14
to std-dis...@isocpp.org
On Sun, Sep 14, 2014 at 4:43 PM, Matheus Izvekov <mizv...@gmail.com> wrote:
> On Sunday, September 14, 2014 7:52:37 PM UTC-3, sean.mid...@gmail.com wrote:
>> Another option rather than changing the rules is just to explicitly state
>> that user-defined specializations of std::is_trivially_* are allowed when
>> the user knows best. I don't know if that's legal now but it seems the
>> simplest solution.
>
> I don't think such specializations are legal, or that there is precedent for
> them.

They are currently done for things like std::hash. I've seen it done
often for traits like iterator_traits and numeric_traits. There's even
a proposal (N3867) to make it easier to specialize traits templates in
other namespaces with the std:: namespace being the motivating
example. :)

Matheus Izvekov

unread,
Sep 14, 2014, 8:59:07 PM9/14/14
to std-dis...@isocpp.org, se...@middleditch.us
On Sunday, September 14, 2014 9:46:37 PM UTC-3, Sean Middleditch wrote:

They are currently done for things like std::hash. I've seen it done
often for traits like iterator_traits and numeric_traits. There's even
a proposal (N3867) to make it easier to specialize traits templates in
other namespaces with the std:: namespace being the motivating
example. :)

Sure, but it would need to be made clear what templates can be specialized by the user, and that everything else should not, lest
we end up with nonsense like std::is_enum specializations :)

But still, it does not address all of the problem, the standard has to say something about which types are valid to memcpy, and
with just that workaround, you would be relying on the user to say that, which is not very reassuring...

Still, myriachan's idea of an attribute seems the best one so far, except that it violates current guidelines of what can be an attribute.
It should be possible to provide a copy/move constructor/assignment operators without making the type non-trivially copyable.

Matheus Izvekov

unread,
Sep 14, 2014, 9:44:35 PM9/14/14
to std-dis...@isocpp.org
So after some thinking, I think the best way so far to put the problem is the following:

std::is_trivially_copyable is currently broken because it mixes two slightly related but different concepts:

1) That an object of a given type could be memcpy'ed without leading to undefined behaviour.

2) That a copy of an object made through memcpy would have the same representational value as it's original.

Concept 1) would be what my definition on the original post describes.

But concept 2) is not very well defined by the current rules, in fact I think of no rules which would approximate this very well except letting the user specify this explicitly.

Thiago Macieira

unread,
Sep 14, 2014, 10:16:13 PM9/14/14
to std-dis...@isocpp.org
On Sunday 14 September 2014 15:02:09 Matheus Izvekov wrote:
> Or from another point of view, it seems like the set of classes that can be
> safely copied by std::memcpy is more broad than those that are trivially
> copyable.

Correct.
--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

Thiago Macieira

unread,
Sep 14, 2014, 10:20:07 PM9/14/14
to std-dis...@isocpp.org
On Sunday 14 September 2014 17:59:07 Matheus Izvekov wrote:
> But still, it does not address all of the problem, the standard has to say
> something about which types are valid to memcpy, and
> with just that workaround, you would be relying on the user to say that,
> which is not very reassuring...

I don't think there's a way around that. The standard cannot know what types
can be memcpy'ed or not based on the members of that structure. The user has
to say something.

And in the absence of the user saying something, container classes must assume
that they can't take any liberties with the type.

Miro Knejp

unread,
Sep 14, 2014, 10:28:42 PM9/14/14
to std-dis...@isocpp.org
This dilemma is probably one of the reasons facebook (and I'm sure others, too) have a custom trait "IsRelocatable" used in their own containers that describes whether a type is memcpy compatible:


I assume the default template of IsRelocatable<T> derives from is_trivially_copyable<T>, that's at least what I would do to cover as many types as possible. Then fewer custom specializations are required. Though I guess in the end it would be nice to have such a trait in the standard for which user specializations are permitted and encouraged.

--

---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.

Sean Middleditch

unread,
Sep 14, 2014, 10:40:24 PM9/14/14
to std-dis...@isocpp.org
They're all little fiddly corner case features like this, but is
looking more and more like using attributes to offer small-scope
tweaks to the language semantics (but not large sweeping changes) are
becoming more attractive.

[[trivial_copy]]
class foo { ... };

This would also play well into some other container-focused custom
traits I see used a lot regarding:
1) whether a type is considered to have "trivial destruction after
move" (the destructor is trivial following as move, but only if moving
to a distinct location)
2) whether it has "trivial construction after zero initialization" (if
you calloc the memory, are your objects considered
trivially-constructible?), and
3) whether it has "trivial paired move and destruction" (if you memcpy
the object to a distinct location and then deallocate the old
location, both move and destruction are trivial).

It turns out many smart pointers (unique_ptr and shared_ptr included)
fall into all three of those categories and by applying them you can
have vectors of smart pointers with essentially zero overhead over raw
pointers even in non-optimized debug builds.

Another thing that would be nice (I'm going way off topic here) would
be a way to mark a member function with non-standard signature as
being a trivial/default constructor, e.g.

class vec {
float x, y;

public:
vec() : x{0}, y{0} {}

[[trivial_constructor]] // allowed on any constructor with an empty
body whose members are all trivially-constructible
vec(std::unitialized_t) {}
};

// "safe"
vec a; // 0,0
// explicitly don't initialize
vec b{std::unitialized};


More on topic... there are a lot of weird and custom construction and
memory rules that the compiler _has_ to assume one way or the other
but for which (in the spirit of C++) it would be nice to have explicit
control over in those cases where we actually need or want it. Aside
from adding a bazillion (contextual) keywords, attributes seem to the
way to go.
> --
>
> ---
> You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Discussion" group.
> To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-discussion/wphImiqfX7Y/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to std-discussio...@isocpp.org.
> To post to this group, send email to std-dis...@isocpp.org.
> Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.



--
Sean Middleditch
http://seanmiddleditch.com

Matheus Izvekov

unread,
Sep 14, 2014, 11:32:50 PM9/14/14
to std-dis...@isocpp.org
On Sunday, September 14, 2014 11:20:07 PM UTC-3, Thiago Macieira wrote:

I don't think there's a way around that. The standard cannot know what types
can be memcpy'ed or not based on the members of that structure. The user has
to say something.


Okay, I think my last post before this one addresses your argument better, but let me be more concrete about it.

I think another trait should be introduced, something like std::is_memory_copyable or so.
This trait would say whether copying an object of a given type would lead to undefined behaviour or not, but it would not tell anything whether the given copy has the same representational value as the original.

Then, std::is_trivially_copyable<T> would be redefined to return std::is_memory_copyable<T>{} && something<T>{}
Where I will define something<T> in just a moment.

Then, to take Miro's suggestion, we would create some new user-specializable template "std::is_relocatable", where a user could tell explicitly whether a std::memcpy copy of an object of this type would preserve the representational value.

Finally, something<T>{} would be the value of std::is_relocatable<T> in case it is specialized for T, and if not, it would be the value returned by the current machinery for std::is_trivially_copyable.

If the above was not clear enough, let me know so I can put it another way, maybe I can write some mock code for this.

Matheus Izvekov

unread,
Sep 14, 2014, 11:34:42 PM9/14/14
to std-dis...@isocpp.org
On Sunday, September 14, 2014 11:28:42 PM UTC-3, Miro Knejp wrote:
This dilemma is probably one of the reasons facebook (and I'm sure others, too) have a custom trait "IsRelocatable" used in their own containers that describes whether a type is memcpy compatible:


I assume the default template of IsRelocatable<T> derives from is_trivially_copyable<T>, that's at least what I would do to cover as many types as possible. Then fewer custom specializations are required. Though I guess in the end it would be nice to have such a trait in the standard for which user specializations are permitted and encouraged.


Great, thanks for pointing that out!

Matheus Izvekov

unread,
Sep 14, 2014, 11:40:50 PM9/14/14
to std-dis...@isocpp.org, se...@middleditch.us

Well, I am more convinced by your first suggestion actually, except that it should be some template other than std::is_trivially_copyable that needs to be specialized by the user.
I am afraid if we start creating an attribute for every trait like that, things will start getting out of hand!
Attributes are still core language things, and the amount should be minimized if the same can be achieved via the STL.

But on the general idea yeah, I think it would be good to have traits like those.

Thiago Macieira

unread,
Sep 15, 2014, 12:28:05 AM9/15/14
to std-dis...@isocpp.org
On Sunday 14 September 2014 20:32:50 Matheus Izvekov wrote:
> Okay, I think my last post before this one addresses your argument better,
> but let me be more concrete about it.
>
> I think another trait should be introduced, something like
> std::is_memory_copyable or so.
> This trait would say whether copying an object of a given type would lead
> to undefined behaviour or not, but it would not tell anything whether the
> given copy has the same representational value as the original.

I'm with you so far.

>
> Then, std::is_trivially_copyable<T> would be redefined to return
> std::is_memory_copyable<T>{} && something<T>{}
> Where I will define something<T> in just a moment.

I disagree. Invert the suggestion: leave Is_trivially_copyable like it is
right now and add the new trait on top. All trivially copyable types can be
memcpy'ed, but the inverse is not true: some memcpy'able types aren't
trivially copyable.

> Then, to take Miro's suggestion, we would create some new
> user-specializable template "std::is_relocatable", where a user could tell
> explicitly whether a std::memcpy copy of an object of this type would
> preserve the representational value.

Wait, now you've lost me. Why do we need three traits?

> Finally, something<T>{} would be the value of std::is_relocatable<T> in
> case it is specialized for T, and if not, it would be the value returned by
> the current machinery for std::is_trivially_copyable.
>
> If the above was not clear enough, let me know so I can put it another way,
> maybe I can write some mock code for this.

It's not.

Matheus Izvekov

unread,
Sep 15, 2014, 12:37:31 AM9/15/14
to std-dis...@isocpp.org
On Monday, September 15, 2014 1:28:05 AM UTC-3, Thiago Macieira wrote:

I disagree. Invert the suggestion: leave Is_trivially_copyable like it is
right now and add the new trait on top. All trivially copyable types can be
memcpy'ed, but the inverse is not true: some memcpy'able types aren't
trivially copyable.

What I am trying to avoid here is some nonsense like for example a user specifying that
some class with virtual functions/bases is trivially copyable.

In other words, I think we should trust the user as much in that he knows whether the type preserves the value when memcopied,
but not trust him whether the type is memcpyable at all in the first place!
 

> Then, to take Miro's suggestion, we would create some new
> user-specializable template "std::is_relocatable", where a user could tell
> explicitly whether a std::memcpy copy of an object of this type would
> preserve the representational value.

Wait, now you've lost me. Why do we need three traits?

See the above!


It's not.


Okay, let me know if that's still not clear!

Sean Middleditch

unread,
Sep 15, 2014, 12:48:04 AM9/15/14
to std-dis...@isocpp.org
On Sun, Sep 14, 2014 at 8:40 PM, Matheus Izvekov <mizv...@gmail.com> wrote:

> I am afraid if we start creating an attribute for every trait like that,
> things will start getting out of hand!

I don't think so. Whether you make an attribute or a library type
trait, it's the same amount of addition to the language (assuming that
the language already has some other extensions in attributes that have
come up in the Reflection SG).

If user-defined functions/types could be used as attributes then you'd
basically just be adding a new more convenient way to use library type
traits, even. :) You'd have:

[[std::attr::is_relocatable]]
struct foo { ... };

rather than (not yet possible without namespace shenanigans):

struct foo { ... };
template<> struct std::is_relocatable<foo> : std::true_type {};

You'd need user-defined types/functions to be usable as attributes and
then a way to check if a type has an attribute. Then the library
implementation would be something vaguely like:

namespace attr { struct is_relocatable {}; }

template <typename T> struct is_relocatable :
std::integral_constant<bool, std::is_trivially_copyable_v<T> ||
std::has_attribute_v<T, attr::is_relocatable>> {};

This kind of attribute is already pretty similar to carries_dependency
I think: it doesn't change language semantics but rather just provides
hints to the implementation (in this case the library developer) that
some shortcuts can be taken that it can't figure out on its own.

>
> But on the general idea yeah, I think it would be good to have traits like
> those.
>

Matheus Izvekov

unread,
Sep 15, 2014, 1:07:43 AM9/15/14
to std-dis...@isocpp.org, se...@middleditch.us
On Monday, September 15, 2014 1:48:04 AM UTC-3, Sean Middleditch wrote:
On Sun, Sep 14, 2014 at 8:40 PM, Matheus Izvekov <mizv...@gmail.com> wrote:

If user-defined functions/types could be used as attributes then you'd
basically just be adding a new more convenient way to use library type
traits, even. :) You'd have:

  [[std::attr::is_relocatable]]
  struct foo { ... };

rather than (not yet possible without namespace shenanigans):

  struct foo { ... };
  template<> struct std::is_relocatable<foo> : std::true_type {};

You'd need user-defined types/functions to be usable as attributes and
then a way to check if a type has an attribute. Then the library
implementation would be something vaguely like:

  namespace attr { struct is_relocatable {}; }

  template <typename T> struct is_relocatable :
std::integral_constant<bool, std::is_trivially_copyable_v<T> ||
std::has_attribute_v<T, attr::is_relocatable>> {};

This kind of attribute is already pretty similar to carries_dependency
I think: it doesn't change language semantics but rather just provides
hints to the implementation (in this case the library developer) that
some shortcuts can be taken that it can't figure out on its own.

That's not really true, I think, that still violates those guidelines about attributes not changing semantics.

If those attributes were something that you would only give a hint to the compiler, and it would generate more efficient code if
they were correctly used, and UB in case they were misused, then yes they would be a perfect fit for what the committee
members currently think those should be limited to.

But the moment you introduce some hook to query if some entity has that attribute, you are changing the semantics.

It would be the same story if someone proposed a trait to check if some given function has [[noreturn]], it would make
it possible for some generic code template parametrized on a function to check if it is [[noreturn]] and select different code
paths depending on that, a semantic change!

But beware that all I am saying is what I, as an outsider, understand what those rules are. Take this with some grains of salt unless
someone with an official standing acknowledges that I am not wrong on this.

Sean Middleditch

unread,
Sep 15, 2014, 1:22:03 AM9/15/14
to std-dis...@isocpp.org
On Sun, Sep 14, 2014 at 10:07 PM, Matheus Izvekov <mizv...@gmail.com> wrote:
> But the moment you introduce some hook to query if some entity has that
> attribute, you are changing the semantics.

It'll happen at some point anyway, I'd wager. Some of the best use
cases for compile-time reflection/introspection are dependent on it.

> It would be the same story if someone proposed a trait to check if some
> given function has [[noreturn]], it would make
> it possible for some generic code template parametrized on a function to
> check if it is [[noreturn]] and select different code
> paths depending on that, a semantic change!

I'd hope very hard that the committee doesn't take quite that hard and
literal of an interpretation/stance on the topic. :) End-user code can
do all kinds of stupid things with type traits, e.g.

template <typename T, class =
std::enable_if_t<std::is_nothrow_move_assignable_v<T>>
void sort(T& container) {
take_a_nap();
launch_the_missiles();
}

and whether you've specified the misused trait in an attribute or not
is super irrelevant IMHO.

And we've veered off topic (my fault) as this really has nothing to do
with the proposed trait itself. :)

Thiago Macieira

unread,
Sep 15, 2014, 1:34:45 AM9/15/14
to std-dis...@isocpp.org
On Sunday 14 September 2014 21:37:31 Matheus Izvekov wrote:
> On Monday, September 15, 2014 1:28:05 AM UTC-3, Thiago Macieira wrote:
> > I disagree. Invert the suggestion: leave Is_trivially_copyable like it is
> > right now and add the new trait on top. All trivially copyable types can
> > be
> > memcpy'ed, but the inverse is not true: some memcpy'able types aren't
> > trivially copyable.
>
> What I am trying to avoid here is some nonsense like for example a user
> specifying that
> some class with virtual functions/bases is trivially copyable.
>
> In other words, I think we should trust the user as much in that he knows
> whether the type preserves the value when memcopied,
> but not trust him whether the type is memcpyable at all in the first place!

Why can't a type with virtuals be memcpy'able? It depends on the ABI, of
course, but if the user knows the ABI allows for it, why not?

> Okay, let me know if that's still not clear!

Still not.

Matheus Izvekov

unread,
Sep 15, 2014, 1:47:29 AM9/15/14
to std-dis...@isocpp.org
On Monday, September 15, 2014 2:34:45 AM UTC-3, Thiago Macieira wrote:

Why can't a type with virtuals be memcpy'able? It depends on the ABI, of
course, but if the user knows the ABI allows for it, why not?


Well, I just gave virtuals as an example of something that the committee would be unwilling to give a guarantee about being memcpyable, even as implementation defined.
But I am not setting that in stone.
Maybe the exact value of std::is_memory_copyable would be implementation defined, but not sure of all the implications of this.
It would still be preferable to relying on the user for this, don't you think?

I'd say instead that anything that makes a class not have contiguous storage would make it not be memcpyable, but I don't know any examples.

Still not.


Okay, one more try!

Miro Knejp

unread,
Sep 15, 2014, 1:55:00 AM9/15/14
to std-dis...@isocpp.org

On 15 Sep 2014, at 07:34 , Thiago Macieira <thi...@macieira.org> wrote:

> On Sunday 14 September 2014 21:37:31 Matheus Izvekov wrote:
>> On Monday, September 15, 2014 1:28:05 AM UTC-3, Thiago Macieira wrote:
>>> I disagree. Invert the suggestion: leave Is_trivially_copyable like it is
>>> right now and add the new trait on top. All trivially copyable types can
>>> be
>>> memcpy'ed, but the inverse is not true: some memcpy'able types aren't
>>> trivially copyable.
>>
>> What I am trying to avoid here is some nonsense like for example a user
>> specifying that
>> some class with virtual functions/bases is trivially copyable.
>>
>> In other words, I think we should trust the user as much in that he knows
>> whether the type preserves the value when memcopied,
>> but not trust him whether the type is memcpyable at all in the first place!
>
> Why can't a type with virtuals be memcpy'able? It depends on the ABI, of
> course, but if the user knows the ABI allows for it, why not?
Currently the standard doesn't allow this for is_trivially_copyable because it must have trivial copy/move constructors and that excludes classes with virtual member functions. This goes all the way back to the original issue that is_trivially_copyable is quite restrictive.

I think there are currently two problems open here:
1. A trait enforced by the compiler to tell you that memcpy'ing T would *definitely* cause UB. (This could be some subset of is_trivially_copyable.) This would also be the point where the compiler/ABI decide whether virtual member functions and other implementation-specific properties of the ABI disqualify for memcpy or not. This is not necessarily concerned about trivial copy/move ctors, dtors, operator= and so on.

2. A means for users to specify that a type supports memcpy even though it does not fulfill all requirements of is_trivially_copyable. However I think it should not be possible to override the safeties put in place by the compiler enforced trait in #1, e.g. if the ABI forbids memcpy'ing virtual classes it should not be possible to bypass this restriction.

3. A trait that is a combination of #1 and #2 that would ultimately be queried by users to determine whether they may call memcpy or not. If #2 for T does not exist the fallback is is_trivially_copyable. This trait is applied transitively for types which do not have a user provided specialization of #2.

Makes sense?

Matheus Izvekov

unread,
Sep 15, 2014, 1:59:11 AM9/15/14
to std-dis...@isocpp.org
On Monday, September 15, 2014 2:55:00 AM UTC-3, Miro Knejp wrote:

Currently the standard doesn't allow this for is_trivially_copyable because it must have trivial copy/move constructors and that excludes classes with virtual member functions. This goes all the way back to the original issue that is_trivially_copyable is quite restrictive.

I think there are currently two problems open here:
1. A trait enforced by the compiler to tell you that memcpy'ing T would *definitely* cause UB. (This could be some subset of is_trivially_copyable.) This would also be the point where the compiler/ABI decide whether virtual member functions and other implementation-specific properties of the ABI disqualify for memcpy or not. This is not necessarily concerned about trivial copy/move ctors, dtors, operator= and so on.

2. A means for users to specify that a type supports memcpy even though it does not fulfill all requirements of is_trivially_copyable. However I think it should not be possible to override the safeties put in place by the compiler enforced trait in #1, e.g. if the ABI forbids memcpy'ing virtual classes it should not be possible to bypass this restriction.

3. A trait that is a combination of #1 and #2 that would ultimately be queried by users to determine whether they may call memcpy or not. If #2 for T does not exist the fallback is is_trivially_copyable. This trait is applied transitively for types which do not have a user provided specialization of #2.

Makes sense?


Yes! That's exactly what I am saying here! +1

Miro Knejp

unread,
Sep 15, 2014, 2:35:13 AM9/15/14
to std-dis...@isocpp.org
I think it's good to have some sort of terminology for this thread, so just for the sake of discussion let me name the traits from my list based on facebook's example:

1. __is_memcpyable<T>: This trait is provided by the implementation. It is true if memcpy'ing T would not break the object from the implementation's point of view. It does not concern itself with pesky details like whether your copy constructor is trivial, has the proper semantics, does the right thing or whatever. This is purely technical. I put it with double underscores here because I don't think it requires to actually exist in the library and is for exposition only.

2. supports_relocation<T>: This is a new trait that by default equals is_trivially_copyable<T>. Users are encouraged to specialize this to true_type where the requirements of is_trivially_copyable are too restrictive and they know what they're doing.

3. is_relocatable<T>: This is a new trait that is actually queried by end users to determine whether they are allowed to memcpy T or not. It is defined as (is_trivially_copyable || (__is_memcpyable<T> && supports_relocation<T>)).

This got me thinking whether it would be an option to go back and re-define is_trivially_copyable to pick up these specializations:

is_trivially_copyable := 
/* new rules */
(supports_relocation && __is_memcpyable) ||
/* old rules */
(is_trivially_{copy_constructible && move_constructible && copy_assignable && move_assignable && destructible})

If this was accepted then supports_relocation could be renamed to is_relocatable to be more consistent with the other traits (although it would become the first is_* trait open for user specialization). The benefit of this approach is: code currently querying is_trivially_copyable would automatically pick up new types for which supports_relocation/is_relocatable gets specialized in the future.

Matheus Izvekov

unread,
Sep 15, 2014, 3:06:56 AM9/15/14
to std-dis...@isocpp.org
On Monday, September 15, 2014 3:35:13 AM UTC-3, Miro Knejp wrote:

I think it's good to have some sort of terminology for this thread, so just for the sake of discussion let me name the traits from my list based on facebook's example:

1. __is_memcpyable<T>: This trait is provided by the implementation. It is true if memcpy'ing T would not break the object from the implementation's point of view. It does not concern itself with pesky details like whether your copy constructor is trivial, has the proper semantics, does the right thing or whatever. This is purely technical. I put it with double underscores here because I don't think it requires to actually exist in the library and is for exposition only.

2. supports_relocation<T>: This is a new trait that by default equals is_trivially_copyable<T>. Users are encouraged to specialize this to true_type where the requirements of is_trivially_copyable are too restrictive and they know what they're doing.

3. is_relocatable<T>: This is a new trait that is actually queried by end users to determine whether they are allowed to memcpy T or not. It is defined as (is_trivially_copyable || (__is_memcpyable<T> && supports_relocation<T>)).

This got me thinking whether it would be an option to go back and re-define is_trivially_copyable to pick up these specializations:

Yeah, I don't see any pitfalls in changing is_trivially_copyable, and I guess it would lead to less amount of rewriting of the STL and user code.
 

is_trivially_copyable := 
/* new rules */
(supports_relocation && __is_memcpyable) ||
/* old rules */
(is_trivially_{copy_constructible && move_constructible && copy_assignable && move_assignable && destructible})

If this was accepted then supports_relocation could be renamed to is_relocatable to be more consistent with the other traits (although it would become the first is_* trait open for user specialization). The benefit of this approach is: code currently querying is_trivially_copyable would automatically pick up new types for which supports_relocation/is_relocatable gets specialized in the future.

Yeah, although there is one small detail I left here out of my initial explanation, because I didn't want make things cloudier than they were before we got into the same page regarding the important bits.
I made the design so that the user would be able to override the current is_trivially implementation both positively and negatively.

You could make the case that maybe the simplest approach would be that, in case the current is_trivially_copyable returns true, then
the new is_trivially_copyable implementation would also return true independent of any supports_relocation/is_relocatable specialization.

But if someone came forward with an example where the current is_trivially_copyable returns true for a type, but it still should not be memcpy'ed
for any reason, then it would be most unfortunate.

So I'd keep the things as we are currently proposing, but just letting this small detail out in case I overlooked something.

Myriachan

unread,
Sep 15, 2014, 5:59:33 AM9/15/14
to std-dis...@isocpp.org
There is nothing in the Standard that says that virtual functions cannot be implemented in a way that involves storing a pointer to the base of the most-derived type of that instance (i.e. dynamic_cast<void *>(this)). Such an implementation would definitely blow up horribly / cause undefined behavior if memcpy'd. As much as I seem on the liberal side of things, I have to agree with this rule.

However, I see no reason that an implementation should be disallowed from returning true for is_raw_memcpyable or whatever you want to call it for a class that has virtual functions or even virtual inheritance if the compiler knows for sure that its implementation of that class will have defined behavior when memcpy'd.

So I agree with the multiple-part definition:

1. A trait indicating whether a memcpy'ing a class will have defined behavior in terms of members, submembers, and implementation-specific internal information. That is, without regard to whether a class has constructors or destructors (except that virtual destructors could affect this on account of being virtual).

2. A trait indicating whether memcpy will have defined behavior based upon both #1 and whether the class has constructors/destructors. This is the current is_trivially_copyable. However, it'd be great if it could be forced true by the class. Whether the class can force it true even when #1 is false, I don't know. (This would mean that the programmer is saying, "compiler, I know how you think, and am willing to risk getting this wrong with the result blowing up in my face.")

3. A trait indicating that structure members go in the order specified, with base classes first, and with the first member (possibly of the first base class with a member) at offset 0, meaning reinterpret_cast from struct to first member is legal. Also between such structures with common initial sequences (sockaddr...). This is obviously just the unchanged is_standard_layout.

However, I would argue that there should be a 4th trait, based on the fact that it is formally probably possible:

4. A trait indicating that offsetof is valid and a constant-expression for a class. This is the OR of #1 and #3. The current Standard only permits #3 but #1 can be added with just a tweak to the rules. offsetof must be definable and a fixed value for the runtime of a program if it is possible to write memcpy within C++ itself (that is, that memcpy is not a "magic" function).

Requiring that offsetof be a constant-expression seems logical to me - the alternative would be to allow implementations to restructure classes at program start time. Since data member pointers are constant-expressions, this seems to match the concept.

Melissa

Miro Knejp

unread,
Sep 15, 2014, 6:22:42 AM9/15/14
to std-dis...@isocpp.org

On 15 Sep 2014, at 11:59 , Myriachan <myri...@gmail.com> wrote:

> There is nothing in the Standard that says that virtual functions cannot be implemented in a way that involves storing a pointer to the base of the most-derived type of that instance (i.e. dynamic_cast<void *>(this)). Such an implementation would definitely blow up horribly / cause undefined behavior if memcpy'd. As much as I seem on the liberal side of things, I have to agree with this rule.
>
> However, I see no reason that an implementation should be disallowed from returning true for is_raw_memcpyable or whatever you want to call it for a class that has virtual functions or even virtual inheritance if the compiler knows for sure that its implementation of that class will have defined behavior when memcpy'd.
>
> So I agree with the multiple-part definition:
>
> 1. A trait indicating whether a memcpy'ing a class will have defined behavior in terms of members, submembers, and implementation-specific internal information. That is, without regard to whether a class has constructors or destructors (except that virtual destructors could affect this on account of being virtual).
For me this entire discussion about memcpy makes only sense if an object's static and dynamic types are the same. Whether someone starts slicing an object with memcpy or operator= doesn't really matter much anymore, they're both Bad Things, so in that regard I'd say a virtual dtor isn't special from regular virtual methods.

>
> 2. A trait indicating whether memcpy will have defined behavior based upon both #1 and whether the class has constructors/destructors. This is the current is_trivially_copyable. However, it'd be great if it could be forced true by the class. Whether the class can force it true even when #1 is false, I don't know. (This would mean that the programmer is saying, "compiler, I know how you think, and am willing to risk getting this wrong with the result blowing up in my face.")
I would argue that #1 always prevails. #1 is a hard requirement by the implementation that shouldn't be possible to break without Machiavelli.

>
> 3. A trait indicating that structure members go in the order specified, with base classes first, and with the first member (possibly of the first base class with a member) at offset 0, meaning reinterpret_cast from struct to first member is legal. Also between such structures with common initial sequences (sockaddr...). This is obviously just the unchanged is_standard_layout.
This is talking about layout compatibility. Maybe we should first figure out how to properly specify that a single type T supports bitwise copying despite the presence of nontrivial special member functions and the current definition of trivial types.

>
> However, I would argue that there should be a 4th trait, based on the fact that it is formally probably possible:
>
> 4. A trait indicating that offsetof is valid and a constant-expression for a class. This is the OR of #1 and #3. The current Standard only permits #3 but #1 can be added with just a tweak to the rules. offsetof must be definable and a fixed value for the runtime of a program if it is possible to write memcpy within C++ itself (that is, that memcpy is not a "magic" function).
Again, I think layout compatibility and offsetof are a separate issue in light of this discussion. Copying bits between subobjects of unrelated types needs more effort to get right. Fixing offsetof would of course be great, but my motivation for this topic is primarily optimizing containers and/or copy operations despite having nontrivial copy/move constructors. Once that is done you can at least copy subobjects of the same type between unrelated containing classes.

Matheus Izvekov

unread,
Sep 15, 2014, 6:49:31 AM9/15/14
to std-dis...@isocpp.org
On Monday, September 15, 2014 6:59:33 AM UTC-3, Myriachan wrote:
There is nothing in the Standard that says that virtual functions cannot be implemented in a way that involves storing a pointer to the base of the most-derived type of that instance (i.e. dynamic_cast<void *>(this)).  Such an implementation would definitely blow up horribly / cause undefined behavior if memcpy'd.  As much as I seem on the liberal side of things, I have to agree with this rule.

However, I see no reason that an implementation should be disallowed from returning true for is_raw_memcpyable or whatever you want to call it for a class that has virtual functions or even virtual inheritance if the compiler knows for sure that its implementation of that class will have defined behavior when memcpy'd.

Agreed, So in effect some set of classes would be required to be memcpyable for every implementation, and as a relaxation, it would be allowed for implementations to expand that set a little further.
 

So I agree with the multiple-part definition:

2. A trait indicating whether memcpy will have defined behavior based upon both #1 and whether the class has constructors/destructors.  This is the current is_trivially_copyable.  However, it'd be great if it could be forced true by the class.  Whether the class can force it true even when #1 is false, I don't know.  (This would mean that the programmer is saying, "compiler, I know how you think, and am willing to risk getting this wrong with the result blowing up in my face.")

Okay, I am strongly against allowing the user to force it to true even if #1 is false, sounds too evil even for C++ standards...
 

4. A trait indicating that offsetof is valid and a constant-expression for a class.  This is the OR of #1 and #3.  The current Standard only permits #3 but #1 can be added with just a tweak to the rules.  offsetof must be definable and a fixed value for the runtime of a program if it is possible to write memcpy within C++ itself (that is, that memcpy is not a "magic" function).

Requiring that offsetof be a constant-expression seems logical to me - the alternative would be to allow implementations to restructure classes at program start time.  Since data member pointers are constant-expressions, this seems to match the concept.

Okay, but that looks just like a separate issue. The question here is, can the offset of a member be known at compile time, as opposed to during runtime only, eg due to virtual bases?

It looks like in theory the offset could be known at compile time even if the object does not have contiguous storage, and even if it does not follow standard/C layout rules.
It could even be the case that some member's offset could be known at compile time, but not others.
Does that make sense or am I sleep deprived?
 

Melissa

Thiago Macieira

unread,
Sep 15, 2014, 11:24:31 AM9/15/14
to std-dis...@isocpp.org
On Monday 15 September 2014 08:35:09 Miro Knejp wrote:
> I think it's good to have some sort of terminology for this thread, so just
> for the sake of discussion let me name the traits from my list based on
> facebook's example:
>
> 1. __is_memcpyable<T>: This trait is provided by the implementation. It is
> true if memcpy'ing T would not break the object from the implementation's
> point of view. It does not concern itself with pesky details like whether
> your copy constructor is trivial, has the proper semantics, does the right
> thing or whatever. This is purely technical. I put it with double
> underscores here because I don't think it requires to actually exist in the
> library and is for exposition only.
>
> 2. supports_relocation<T>: This is a new trait that by default equals
> is_trivially_copyable<T>. Users are encouraged to specialize this to
> true_type where the requirements of is_trivially_copyable are too
> restrictive and they know what they're doing.
>
> 3. is_relocatable<T>: This is a new trait that is actually queried by end
> users to determine whether they are allowed to memcpy T or not. It is
> defined as (is_trivially_copyable || (__is_memcpyable<T> &&
> supports_relocation<T>)).

I agree with the above, but not this:

> This got me thinking whether it would be an option to go back and re-define
> is_trivially_copyable to pick up these specializations:

Leave is_trivially_copyable alone. It means the type can be copied by a
trivial constructor and we're not changing what a trivial constructor is.

Bo Persson

unread,
Sep 15, 2014, 11:39:38 AM9/15/14
to std-dis...@isocpp.org
Matheus Izvekov skrev den 2014-09-15 02:59:
>
> It should be possible to provide a copy/move constructor/assignment
> operators without making the type non-trivially copyable.
>

Why?

If the class really IS trivially copyable, why do you want to add user
defined constructors and operators?


Bo Persson


Bo Persson

unread,
Sep 15, 2014, 12:12:43 PM9/15/14
to std-dis...@isocpp.org
Miro Knejp wrote 2014-09-15 08:35:
>
> I think it's good to have some sort of terminology for this thread, so
> just for the sake of discussion let me name the traits from my list
> based on facebook's example:
>
> 1. __is_memcpyable<T>: This trait is provided by the implementation. It
> is true if memcpy'ing T would not break the object from the
> implementation's point of view. It does not concern itself with pesky
> details like whether your copy constructor is trivial, has the proper
> semantics, does the right thing or whatever. This is purely technical. I
> put it with double underscores here because I don't think it requires to
> actually exist in the library and is for exposition only.

If the compiler knows this, surely it will already generate a memcpy (or
better) for the = default copy constructor and assignment.

Why the heck would you have a non-trivial copy constructor if the
compiler default would work?

>
> 2. supports_relocation<T>: This is a new trait that by default equals
> is_trivially_copyable<T>. Users are encouraged to specialize this to
> true_type where the requirements of is_trivially_copyable are too
> restrictive and they know what they're doing.

The weak point here is "and they know what they're doing". I bet many
users would not know what happens, especially if they move to a new
compiler.

>
> 3. is_relocatable<T>: This is a new trait that is actually queried by
> end users to determine whether they are allowed to memcpy T or not. It
> is defined as (is_trivially_copyable || (__is_memcpyable<T> &&
> supports_relocation<T>)).
>


Bo Persson



Matheus Izvekov

unread,
Sep 15, 2014, 4:23:08 PM9/15/14
to std-dis...@isocpp.org, b...@gmb.dk
On Monday, September 15, 2014 12:39:38 PM UTC-3, Bo Persson wrote:

Why?

If the class really IS trivially copyable, why do you want to add user
defined constructors and operators?


In the example provided in the original post, it's because the implicitly generated copy/move constructors/assignment operators would be allowed
to assume that the object is naturally aligned, and optimize for this case, which would be broken. Implementing those operators explicitly through a
memcpy takes care of that.

That's just one example though, and a more general argument of why the current design for is_trivially_copyable is broken, independent of any examples, is presented earlier in this thread.

Myriachan

unread,
Sep 15, 2014, 5:27:54 PM9/15/14
to std-dis...@isocpp.org, b...@gmb.dk

Clarifying that use case:

Yes.  If this were MIPS, the default compiler-generated unaligned<int>::unaligned(const unaligned<int> &) would be an lw and sw pair of instructions to copy the base member, which would crash on MIPS due to being misaligned.  unaligned does not know that it is misaligned - only structs foo and bar know this.

Microsoft created a third CV qualifier called __unaligned to resolve this, but they don't seem to fully implement it, even when it would matter.  Visual C++ lets you qualify member functions as __unaligned just like they can be const or volatile.  Grammatically, it works, but I've never seen __unaligned make Visual C++ actually do anything to the output code beyond affect overload selection.

Melissa

Matheus Izvekov

unread,
Sep 15, 2014, 5:38:50 PM9/15/14
to std-dis...@isocpp.org, b...@gmb.dk
On Monday, September 15, 2014 6:27:54 PM UTC-3, Myriachan wrote:

Clarifying that use case:

Yes.  If this were MIPS, the default compiler-generated unaligned<int>::unaligned(const unaligned<int> &) would be an lw and sw pair of instructions to copy the base member, which would crash on MIPS due to being misaligned.  unaligned does not know that it is misaligned - only structs foo and bar know this.

Exactly, though newer MIPS32 lifted the restriction on unaligned accesses, older ones would need swl/swr lwl/lwr pairs to be generated.

Microsoft created a third CV qualifier called __unaligned to resolve this, but they don't seem to fully implement it, even when it would matter.  Visual C++ lets you qualify member functions as __unaligned just like they can be const or volatile.  Grammatically, it works, but I've never seen __unaligned make Visual C++ actually do anything to the output code beyond affect overload selection.


What architecture have you tried this on?

Myriachan

unread,
Sep 15, 2014, 7:53:18 PM9/15/14
to std-dis...@isocpp.org, b...@gmb.dk
On Monday, September 15, 2014 2:38:50 PM UTC-7, Matheus Izvekov wrote:

Microsoft created a third CV qualifier called __unaligned to resolve this, but they don't seem to fully implement it, even when it would matter.  Visual C++ lets you qualify member functions as __unaligned just like they can be const or volatile.  Grammatically, it works, but I've never seen __unaligned make Visual C++ actually do anything to the output code beyond affect overload selection.


What architecture have you tried this on?

x86-64.  The example code below crashes.  The __declspec(noinline) is necessary, because if it inlines, the optimizer actually somehow notices the misalignment and emits movups instead of movaps.  This happens regardless of whether the pointer is __unaligned-qualified: adding #define __unaligned has no effect either way.

#include <cstdio>
#include <cstring>
#include <xmmintrin.h>

#define alignas(...) __declspec(align(__VA_ARGS__))

__declspec
(noinline) void copy_sse_to_unaligned(__unaligned __m128 &dest, const __m128 &src)
{
    dest
= src;
}

int main(int argc, const char *const *)
{
    alignas
(16) unsigned char buffer[sizeof(__m128) * 2];

    __m128 src
;
    __unaligned __m128
*dest = reinterpret_cast<__m128 *>(&buffer[1]);

    std
::memset(&src, 0x3F, sizeof(src));

    copy_sse_to_unaligned
(*dest, src);

    std
::printf("%g\n", dest->m128_f32[0]);
   
return 0;
}



Matheus Izvekov

unread,
Sep 15, 2014, 10:33:21 PM9/15/14
to std-dis...@isocpp.org, b...@gmb.dk
On Monday, September 15, 2014 8:53:18 PM UTC-3, Myriachan wrote:

x86-64.  The example code below crashes.  The __declspec(noinline) is necessary, because if it inlines, the optimizer actually somehow notices the misalignment and emits movups instead of movaps.  This happens regardless of whether the pointer is __unaligned-qualified: adding #define __unaligned has no effect either way.

Yeah, taking a look at the microsoft reference page: http://msdn.microsoft.com/en-us/library/ms177389.aspx
      "The __unaligned modifier is valid for the x64 and Itanium compilers but affects only applications that target an IPF computer."

Too bad, would have been useful if it was decently supported.

Matheus Izvekov

unread,
Sep 20, 2014, 7:43:26 AM9/20/14
to std-dis...@isocpp.org
Another example that the current design of is_trivially_copyable is broken:

struct foo {
       
int bar;
        foo
() = default;

        foo
(const foo &) = delete;
        foo
(foo &&) = delete;
        foo
& operator=(const foo &) = delete;
        foo
& operator=(foo &&) = delete;
};

According to current language rules, foo is trivially copyable, since it has no non-trivial copy/move constructors/assignment operators, and it has a trivial destructor.

But this seems the opposite of the intended, this class disallows copying entirely. How is it reasonable to infer that this class is trivially copyable from the lack of these operators?

This demonstrates how this trait is completely broken.
The example on the first post showed how sometimes is_trivially_copyable is false when it should be true.
This last example shows the opposite, how it sometimes is true when it should be false.

Jens Maurer

unread,
Sep 21, 2014, 8:07:22 AM9/21/14
to std-dis...@isocpp.org
On 09/20/2014 01:43 PM, Matheus Izvekov wrote:
> Another example that the current design of is_trivially_copyable is broken:
>
> ||
> structfoo {
> intbar;
> foo()=default;
>
> foo(constfoo &)=delete;
> foo(foo &&)=delete;
> foo&operator=(constfoo &)=delete;
> foo&operator=(foo &&)=delete;
> };
>
> According to current language rules, foo is trivially copyable, since it has no non-trivial copy/move constructors/assignment operators, and it has a trivial destructor.
>
> But this seems the opposite of the intended, this class disallows copying entirely. How is it reasonable to infer that this class is trivially copyable from the lack of these operators?
>
> This demonstrates how this trait is completely broken.

"trivially copyable" is a concept (defined term) in the core language section
of the standard.

There is indeed a corresponding trait in the library section, but we should
first inspect whether the core concept is sound before messing with
the library trait.

Thanks,
Jens

Matheus Izvekov

unread,
Sep 21, 2014, 8:35:27 AM9/21/14
to std-dis...@isocpp.org
On Sunday, September 21, 2014 9:07:22 AM UTC-3, Jens Maurer wrote:
"trivially copyable" is a concept (defined term) in the core language section
of the standard.

There is indeed a corresponding trait in the library section, but we should
first inspect whether the core concept is sound before messing with
the library trait.

Thanks,
Jens

Yeah sorry for being too sloppy and not rigorous with the terms.
I have been using "is_trivially_copyable" to refer to the "trivially copyable" concept.

What I mean of course is that the "trivially copyable" concept is badly flawed.
The trait itself works fine, it serves it's purpose of checking if a given type models "trivially copyable", but the concept itself is no good.

I argued earlier in this thread why I think it's broken, and how I think is the best way to fix it.
But I'd have no trouble having a go at it over again if you would like, I understand it might have gotten buried in the noise.

On the seventh post on this thread, I gave a short run down of what I think is the fundamental problem with it.
I will quote it here, and just amend it so it refers to the correct term for the concept.

The concept "trivially copyable" is currently broken because it mixes two slightly related but different concepts:

1) That an object of a given type could be memcpy'ed without leading to undefined behaviour.

2) That a copy of an object made through memcpy would have the same representational value as it's original.

Thiago Macieira

unread,
Sep 21, 2014, 12:22:54 PM9/21/14
to std-dis...@isocpp.org
On Sunday 21 September 2014 05:35:27 Matheus Izvekov wrote:
> The concept "trivially copyable" is currently broken because it mixes two
>
> > slightly related but different concepts:
> >
> > 1) That an object of a given type could be memcpy'ed without leading to
> > undefined behaviour.
> >
> > 2) That a copy of an object made through memcpy would have the same
> > representational value as it's original.

Isn't #2 a consequence of #1?

Matheus Izvekov

unread,
Sep 21, 2014, 1:06:46 PM9/21/14
to std-dis...@isocpp.org
On Sunday, September 21, 2014 1:22:54 PM UTC-3, Thiago Macieira wrote:
Isn't  #2 a consequence of #1?


No, that goes back to what myriachan said on the third post of this thread.
Consider an object which it's interface is not invariant with respect to it's own address.

A trivial example would be:

class foo {
   
int *p;
   
int a;
public:
    foo
() : p(&a) {}
    foo(const foo &c) : p(&a), a(c.a) {}
   
int get() { return *p; }
    void set(int val) { *p = a; }
};

There would be nothing undefined when doing a memory copy of an object of type foo.
It would just lead to unexpected behaviour because the programmer did something he probably did not want to do.
He violated foo's public interface, in some sense.

Ville Voutilainen

unread,
Sep 21, 2014, 1:29:06 PM9/21/14
to std-dis...@isocpp.org
Well, that type is not trivially copyable...

Thiago Macieira

unread,
Sep 21, 2014, 1:52:23 PM9/21/14
to std-dis...@isocpp.org
The code above is not trivially copyable and neither can it be memcpy'ed. I
don't see how that proves anything, since this example is entirely out of
scope of anything we're discussing.

My point is that, given a type T such that it is trivially copyable, then:

T a, b;

the two operations:
a = b;
and
memcpy(&a, &b, sizeof a);

Are the same. In other words, a type being trivially copyable is sufficient
condition for memcpy being allowed, producing the same representational value.

Note the "sufficient" part.

Matheus Izvekov

unread,
Sep 21, 2014, 2:13:39 PM9/21/14
to std-dis...@isocpp.org
On Sunday, September 21, 2014 2:29:06 PM UTC-3, Ville Voutilainen wrote:
Well, that type is not trivially copyable...

Yeah sure, according to current language rules, but I was using it as an example on how it would fit according to the two
separate concepts I just had mentioned on the post before that.

Matheus Izvekov

unread,
Sep 21, 2014, 2:25:08 PM9/21/14
to std-dis...@isocpp.org
On Sunday, September 21, 2014 2:52:23 PM UTC-3, Thiago Macieira wrote:
The code above is not trivially copyable and neither can it be memcpy'ed. I
don't see how that proves anything, since this example is entirely out of
scope of anything we're discussing.

Well, read my reply to Ville just above.
 

My point is that, given a type T such that it is trivially copyable, then:

        T a, b;

the two operations:
        a = b;
and
        memcpy(&a, &b, sizeof a);

Are the same. In other words, a type being trivially copyable is sufficient
condition for memcpy being allowed, producing the same representational value.

Note the "sufficient" part.


Well, this thread will seem pointless if you don't appreciate the dilemma presented in the very first post of this thread.

I am assuming here that you agree that the example on the first post shows that the current design of "trivially copyable" is absurd,
since you already participated in this thread and you seemed to agree it needed to be fixed.

If you don't or forgot what this thread is about, we have to go over everything again from the start.

Matheus Izvekov

unread,
Sep 21, 2014, 3:26:11 PM9/21/14
to std-dis...@isocpp.org
So, this thread has been going for a week, and I think I need to reboot it now because I can't expect that people will be able to jump in and catch up now, since it has grown so big.

The first problem I would like to address in regards to the "trivially copyable" concept is that sometimes you need need to provide copy/move constructors/assignment operators that
still semantically behave like the trivial operators, ie they still behave like memberwise scalar copying, but they also perform "some additional thing" which has no relevant effect.

Examples of "some additional thing" are:
   * In the example on the original post, the extra guarantee that an explicit call to memcpy provides in regards to memory alignment.
   * Increment some global counter for profiling / diagnostic purposes

But when you provide such constructors / operators, the class stops being trivially copyable. This is not desirable.

This now means that if you want to store that class in a container, some important optimizations might be turned off for no good reason.
This means that it would be UB to memcpy such a class, even though it will still work perfectly well in all real world implementations.


The second problem with the trivially copyable concept is that you can have a class with all copy/move constructors/assignment operators deleted, as if to disallow copying entirely, but the class remains trivially copyable, which seems absurd.
It means that some generic code might try to make a copy of the object, but check std::is_trivially_copyable<T>::value first, see that the object is trivially copyable, and make the copy through a memcpy instead of trying to use the copy constructor, which would have caused it to fail.


Now before moving on to discuss how to fix "trivially copyable", let's see if we are on the same page here.

Thiago Macieira

unread,
Sep 21, 2014, 4:27:46 PM9/21/14
to std-dis...@isocpp.org
On Sunday 21 September 2014 11:25:08 Matheus Izvekov wrote:
> On Sunday, September 21, 2014 2:52:23 PM UTC-3, Thiago Macieira wrote:
> > The code above is not trivially copyable and neither can it be memcpy'ed.
> > I
> > don't see how that proves anything, since this example is entirely out of
> > scope of anything we're discussing.
>
> Well, read my reply to Ville just above.

I did and I don't understand it.

> Well, this thread will seem pointless if you don't appreciate the dilemma
> presented in the very first post of this thread.
>
> I am assuming here that you agree that the example on the first post shows
> that the current design of "trivially copyable" is absurd,
> since you already participated in this thread and you seemed to agree it
> needed to be fixed.
>
> If you don't or forgot what this thread is about, we have to go over
> everything again from the start.

I don't agree with the statement that it's broken. I think it is correct.

I think it's incomplete.

I said that "trivially copyable" is sufficient condition for memcpy. It's not a
necessary condition: there are plenty of classes that are memcpy'able but
aren't trivially copyable. And then there's the type of classes that are
memcpy-movable but not memcpy-copyable, like all of the Qt container and
value-type classes, as well as most implementations of the Standard Library
containers with the default allocators.

My point is that the compiler doesn't know and can't know. It's up to the
class author to declare it as such, with either an attribute or by way of a
specialisation. But those need to come on top of the core language definition
of "trivially copyable".

Thiago Macieira

unread,
Sep 21, 2014, 4:30:01 PM9/21/14
to std-dis...@isocpp.org
On Sunday 21 September 2014 16:25:49 Matheus Izvekov wrote:
> The second problem with the trivially copyable concept is that you can have
> a class with all copy/move constructors/assignment operators deleted, as if
> to disallow copying entirely, but the class remains trivially copyable,
> which seems absurd.
> It means that some generic code might try to make a copy of the object, but
> check std::is_trivially_copyable<T>::value first, see that the object is
> trivially copyable, and make the copy through a memcpy instead of trying to
> use the copy constructor, which would have caused it to fail.

That, in specific, looks wrong to me. A class with a deleted copy constructor
can't have a *trivial* copy constructor. It doesn't *have* a copy constructor,
so we can't qualify it as trivial.

> Now before moving on to discuss how to fix "trivially copyable", let's see
> if we are on the same page here.

We're not. We agree on the goal of being able to memcpy more types than the
trivially copyable. We disagree on how to do it.

Ville Voutilainen

unread,
Sep 21, 2014, 4:52:00 PM9/21/14
to std-dis...@isocpp.org
On 21 September 2014 23:29, Thiago Macieira <thi...@macieira.org> wrote:
> That, in specific, looks wrong to me. A class with a deleted copy constructor
> can't have a *trivial* copy constructor. It doesn't *have* a copy constructor,
> so we can't qualify it as trivial.

That's not correct. Deleted functions are defined, and they do participate in
overload resolution. Deleting a function does not mean the function is
suppressed.

charleyb123 .

unread,
Sep 21, 2014, 5:03:08 PM9/21/14
to std-dis...@isocpp.org
On 21 September 2014 23:29, Thiago Macieira <thi...@macieira.org> wrote:
> That, in specific, looks wrong to me. A class with a deleted copy constructor
> can't have a *trivial* copy constructor. It doesn't *have* a copy constructor,
> so we can't qualify it as trivial.

On Sun, Sep 21, 2014 at 2:51 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote: 
That's not correct. Deleted functions are defined, and they do participate in
overload resolution. Deleting a function does not mean the function is
suppressed.

Don't you mean *defaulted* functions are defined?  If it's deleted, then it is not defined, and attempting to invoke it is a compile-time error.

Isn't the *whole point* of deleted-functions to "suppress" them?

That is:

class Foo
{
  public:
    Foo() = default;
    Foo(const Foo&) = delete;
    Foo& operator=(const Foo&) = delete;
};

// ...
Foo my_foo; // ok
Foo my_foo2(my_foo);  // compile-error
Foo my_foo3; // ok
my_foo3 = my_foo; // compile-error


Matheus Izvekov

unread,
Sep 21, 2014, 5:13:17 PM9/21/14
to std-dis...@isocpp.org
On Sunday, September 21, 2014 5:27:46 PM UTC-3, Thiago Macieira wrote:
I don't agree with the statement that it's broken. I think it is correct.

I think it's incomplete.

Thats a distinction without a difference. The "trivially copyable" concept defines exactly the kinds
of objects which can be memory copied. If you think it doesn't cover all the cases, then you would have to change it
for something different..., but letś not argue over natural language semantics :)
 

I said that "trivially copyable" is sufficient condition for memcpy. It's not a
necessary condition: there are plenty of classes that are memcpy'able but
aren't trivially copyable.

As I said above, and correct me if I am wrong, but according to the standard using memcpy on any class that is
not trivially copyable leads to UB.

If you are talking about the classes that are memcpy'able in practice in real world implementations, like any sane compiler there is, I agree,
you can get away just fine in many more cases. Not according to the standard though.
 

My point is that the compiler doesn't know and can't know. It's up to the
class author to declare it as such, with either an attribute or by way of a
specialisation.

We agree here.

Matheus Izvekov

unread,
Sep 21, 2014, 5:21:07 PM9/21/14
to std-dis...@isocpp.org
On Sunday, September 21, 2014 5:30:01 PM UTC-3, Thiago Macieira wrote:
That, in specific, looks wrong to me. A class with a deleted copy constructor
can't have a *trivial* copy constructor. It doesn't *have* a copy constructor,
so we can't qualify it as trivial.

Exactly, and the standard text is worded : "and no non-trivial copy constructors",
So that means such a class would be trivially copyable...

Matheus Izvekov

unread,
Sep 21, 2014, 5:23:50 PM9/21/14
to std-dis...@isocpp.org
On Sunday, September 21, 2014 5:52:00 PM UTC-3, Ville Voutilainen wrote:
That's not correct. Deleted functions are defined, and they do participate in
overload resolution. Deleting a function does not mean the function is
suppressed.

But a class having a deleted copy constructor implies that the class does not have a trivial copy constructor, right?

Matheus Izvekov

unread,
Sep 21, 2014, 5:48:50 PM9/21/14
to std-dis...@isocpp.org

To add to the above, a class with a deleted copy constructor should not have a non-trivial copy constructor either....

So, putting all of this together, it means that a class with a deleted copy constructor has a copy constructor, but it is neither trivial nor non-trivial O_o

Thiago Macieira

unread,
Sep 21, 2014, 6:01:52 PM9/21/14
to std-dis...@isocpp.org
On Sunday 21 September 2014 14:13:16 Matheus Izvekov wrote:
> On Sunday, September 21, 2014 5:27:46 PM UTC-3, Thiago Macieira wrote:
> > I don't agree with the statement that it's broken. I think it is correct.
> >
> > I think it's incomplete.
>
> Thats a distinction without a difference. The "trivially copyable" concept
> defines exactly the kinds
> of objects which can be memory copied. If you think it doesn't cover all
> the cases, then you would have to change it
> for something different..., but letś not argue over natural language
> semantics :)

No, let's. That's exactly what the problem is here and we're talking about
writing a standard. It's *all* about semantics.

> > I said that "trivially copyable" is sufficient condition for memcpy. It's
> > not a
> > necessary condition: there are plenty of classes that are memcpy'able but
> > aren't trivially copyable.
>
> As I said above, and correct me if I am wrong, but according to the
> standard using memcpy on any class that is
> not trivially copyable leads to UB.

I don't know of a passage of the standard that says that. What I know is that
it is well-defined to memcpy a trivially-copyable object.

> If you are talking about the classes that are memcpy'able in practice in
> real world implementations, like any sane compiler there is, I agree,
> you can get away just fine in many more cases. Not according to the
> standard though.

Standard layout types are compatible with C and, since the only way for them
to be copyable in C is via user-defined code, it stands to reason that all
standard layout types could be copied with memcpy if the members allow for it.
Therefore, as long as the user-defined copy constructor, move constructor, copy
assignment operator, move assignment operator and destructor allow for it, the
type could be copied by memcpy too.

> > My point is that the compiler doesn't know and can't know. It's up to the
> > class author to declare it as such, with either an attribute or by way of
> > a
> > specialisation.
>
> We agree here.

Thiago Macieira

unread,
Sep 21, 2014, 6:03:04 PM9/21/14
to std-dis...@isocpp.org
On Sunday 21 September 2014 14:48:50 Matheus Izvekov wrote:
> So, putting all of this together, it means that a class with a deleted copy
> constructor has a copy constructor, but it is neither trivial nor
> non-trivial O_o

The empty set qualifies all properties.

Ville Voutilainen

unread,
Sep 21, 2014, 6:10:21 PM9/21/14
to std-dis...@isocpp.org
On 22 September 2014 00:03, charleyb123 . <charl...@gmail.com> wrote:
>> That's not correct. Deleted functions are defined, and they do participate
>> in
>> overload resolution. Deleting a function does not mean the function is
>> suppressed.
> Don't you mean *defaulted* functions are defined? If it's deleted, then it

No, I don't mean that. Had I meant that, I would've written so. The whole
section about deleted functions in the standard draft very specifically
talks about deleted definitions. Even the section title is
8.4.3 Deleted definitions [dcl.fct.def.delete].

> Isn't the *whole point* of deleted-functions to "suppress" them?

No. The whole point of deleted functions is that if overload resolution ends
up choosing a deleted definition, the program is ill-formed. Suppression
wouldn't do that.

Ville Voutilainen

unread,
Sep 21, 2014, 6:12:39 PM9/21/14
to std-dis...@isocpp.org
Not with the current rules. Also, having a deleted copy constructor
doesn't necessarily
mean !is_trivially_copy_constructible. The traits specifically answer
the question
"is such-and-such expression valid?", not "does this class have
such-and-such special
member function".

Matheus Izvekov

unread,
Sep 21, 2014, 6:16:09 PM9/21/14
to std-dis...@isocpp.org
On Sunday, September 21, 2014 7:01:52 PM UTC-3, Thiago Macieira wrote:
I don't know of a passage of the standard that says that. What I know is that
it is well-defined to memcpy a trivially-copyable object.

N3797 Section 3.9:
For any object (other than a base-class subobject) of trivially copyable type T, whether or not the object
holds a valid value of type T, the underlying bytes (1.7) making up the object can be copied into an array
of char or unsigned char.If the content of the array of char or unsigned char is copied back into the
object, the object shall subsequently hold its original value.

That's the only passage in the standard that gives any guarantees about memcpy.
 
Standard layout types are compatible with C and, since the only way for them
to be copyable in C is via user-defined code, it stands to reason that all
standard layout types could be copied with memcpy if the members allow for it.
Therefore, as long as the user-defined copy constructor, move constructor, copy
assignment operator, move assignment operator and destructor allow for it, the
type could be copied by memcpy too.

A standard-layout type could only be memory copied if it were also trivially copyable, per the above.

Matheus Izvekov

unread,
Sep 21, 2014, 6:27:01 PM9/21/14
to std-dis...@isocpp.org
On Sunday, September 21, 2014 7:12:39 PM UTC-3, Ville Voutilainen wrote:
Not with the current rules. Also, having a deleted copy constructor
doesn't necessarily
mean !is_trivially_copy_constructible. The traits specifically answer
the question
"is such-and-such expression valid?", not "does this class have
such-and-such special
member function".

But what expression is valid or invalid here? How can an expression be valid or not depending on whether the copy constructor is trivial?

Matheus Izvekov

unread,
Sep 21, 2014, 6:30:57 PM9/21/14
to std-dis...@isocpp.org
On Sunday, September 21, 2014 7:03:04 PM UTC-3, Thiago Macieira wrote:
On Sunday 21 September 2014 14:48:50 Matheus Izvekov wrote:
> So, putting all of this together, it means that a class with a deleted copy
> constructor has a copy constructor, but it is neither trivial nor
> non-trivial O_o

The empty set qualifies all properties.

It does not satisfy the property of having non-zero cardinality :)

Ville Voutilainen

unread,
Sep 21, 2014, 6:33:33 PM9/21/14
to std-dis...@isocpp.org
The expression used for copying. And it's not valid or not depending on whether
the copy constructor is trivial. It all boils down to
is_trivially_constructible, which
says "is_constructible<T,Args...>::value is true and the variable definition for
is_constructible, as defined below, is known to call no operation that
is not trivial ( 3.9, 12)."

Check this out:
struct X {
X() = default;
X(const X&)=delete;
};

int main()
{
cout << is_trivially_copyable<X>::value;
cout << is_trivially_copy_constructible<X>::value;
}

clang/libc++ prints true and false. So, X is memcpy-able, but it
cannot be copy-constructed.
There have been suggestions to make X not trivially copyable if X has
deleted copy operations,
but such suggestions haven't been discussed in a meeting yet.

Thiago Macieira

unread,
Sep 21, 2014, 6:46:33 PM9/21/14
to std-dis...@isocpp.org
The passage you quoted does not say that.

The passage you quoted is the sufficient condition that all trivially copyable
types can be copied via trivial functions like memcpy.

It does not say anything about types that aren't trivially copyable.

I am saying that all standard layout types can be copied with user-defined
functions without triggering UB, since the whole point of standard layout is
that they are layout-compatible with C types (PODs). Whether the copied type
makes sense or not depends only on the user-defined copy/move constructors and
assignment operators, and on the destructor.

Matheus Izvekov

unread,
Sep 21, 2014, 6:57:00 PM9/21/14
to std-dis...@isocpp.org
On Sunday, September 21, 2014 7:33:33 PM UTC-3, Ville Voutilainen wrote:
The expression used for copying. And it's not valid or not depending on whether
the copy constructor is trivial. It all boils down to
is_trivially_constructible, which
says "is_constructible<T,Args...>::value is true and the variable definition for
is_constructible, as defined below, is known to call no operation that
is not trivial ( 3.9, 12)."

You mean is_trivially_copyable and is_copyable here.
 

Check this out:
struct X {
    X() = default;
    X(const X&)=delete;
};

int main()
{
    cout << is_trivially_copyable<X>::value;
    cout << is_trivially_copy_constructible<X>::value;
}

clang/libc++ prints true and false. So, X is memcpy-able, but it
cannot be copy-constructed.
There have been suggestions to make X not trivially copyable if X has
deleted copy operations,
but such suggestions haven't been discussed in a meeting yet.

So that means exactly what I thought before this whole discussion began, a class with a deleted copy constructor has no non-trivial copy constructor.

Beware also though that clang currently reports that a class with a deleted destructor is trivially copyable, which clearly seems absurd, and
the standard is worded to disallow it. I already submitted this bug upstream.

By the way, has been there any discussion on allowing the user to specify whether his class should be trivially copyable or not?

Ville Voutilainen

unread,
Sep 21, 2014, 7:00:01 PM9/21/14
to std-dis...@isocpp.org
On 22 September 2014 01:46, Thiago Macieira <thi...@macieira.org> wrote:
>> A standard-layout type could only be memory copied if it were also
>> trivially copyable, per the above.
>
> The passage you quoted does not say that.
>
> The passage you quoted is the sufficient condition that all trivially copyable
> types can be copied via trivial functions like memcpy.
>
> It does not say anything about types that aren't trivially copyable.
>
> I am saying that all standard layout types can be copied with user-defined
> functions without triggering UB, since the whole point of standard layout is
> that they are layout-compatible with C types (PODs). Whether the copied type
> makes sense or not depends only on the user-defined copy/move constructors and
> assignment operators, and on the destructor.


Well, that depends when you expect the UB to occur. Sure, it might not
occur on copy,
but

struct X
{
std::string foo;
};

is standard-layout, but not trivially copyable. If you want to memcpy
safely, you need
to have a trivially copyable type. PODs are both standard-layout and trivial.

Matheus Izvekov

unread,
Sep 21, 2014, 7:03:47 PM9/21/14
to std-dis...@isocpp.org
On Sun, Sep 21, 2014 at 7:46 PM, Thiago Macieira <thi...@macieira.org> wrote:
The passage you quoted does not say that.

The passage you quoted is the sufficient condition that all trivially copyable
types can be copied via trivial functions like memcpy.

It does not say anything about types that aren't trivially copyable.

Sure, but there are no passages that say anything about memcpy on non-trivially copyable types, so it is undefined.
 

I am saying that all standard layout types can be copied with user-defined
functions without triggering UB, since the whole point of standard layout is
that they are layout-compatible with C types (PODs). Whether the copied type
makes sense or not depends only on the user-defined copy/move constructors and
assignment operators, and on the destructor.

Well that could make sense, if it said anything about that in the standard, but I can't find it.

Ville Voutilainen

unread,
Sep 21, 2014, 7:04:11 PM9/21/14
to std-dis...@isocpp.org
On 22 September 2014 01:57, Matheus Izvekov <mizv...@gmail.com> wrote:
> On Sunday, September 21, 2014 7:33:33 PM UTC-3, Ville Voutilainen wrote:
>>
>> The expression used for copying. And it's not valid or not depending on
>> whether
>> the copy constructor is trivial. It all boils down to
>> is_trivially_constructible, which
>> says "is_constructible<T,Args...>::value is true and the variable
>> definition for
>> is_constructible, as defined below, is known to call no operation that
>> is not trivial ( 3.9, 12)."
>
>
> You mean is_trivially_copyable and is_copyable here.

That's a very different trait that gives a different answer, as can be
seen below.

>> Check this out:
>> struct X {
>> X() = default;
>> X(const X&)=delete;
>> };
>>
>> int main()
>> {
>> cout << is_trivially_copyable<X>::value;
>> cout << is_trivially_copy_constructible<X>::value;
>> }
>>
>> clang/libc++ prints true and false. So, X is memcpy-able, but it
>> cannot be copy-constructed.
>> There have been suggestions to make X not trivially copyable if X has
>> deleted copy operations,
>> but such suggestions haven't been discussed in a meeting yet.
>
>
> So that means exactly what I thought before this whole discussion began, a
> class with a deleted copy constructor has no non-trivial copy constructor.
>
> Beware also though that clang currently reports that a class with a deleted
> destructor is trivially copyable, which clearly seems absurd, and
> the standard is worded to disallow it. I already submitted this bug
> upstream.

Where is the standard worded so?

> By the way, has been there any discussion on allowing the user to specify
> whether his class should be trivially copyable or not?

I don't recall seeing a suggestion that a user could claim his class
to be trivially
copyable when it violates the language rules for trivially copyable types.

Thiago Macieira

unread,
Sep 21, 2014, 7:07:08 PM9/21/14
to std-dis...@isocpp.org
On Monday 22 September 2014 01:59:59 Ville Voutilainen wrote:
> Well, that depends when you expect the UB to occur. Sure, it might not
> occur on copy,
> but
>
> struct X
> {
> std::string foo;
> };
>
> is standard-layout, but not trivially copyable. If you want to memcpy
> safely, you need
> to have a trivially copyable type. PODs are both standard-layout and
> trivial.

Read what I said: it depends on the copy constructor of the type. X's copy
constructor does not allow for it.

However:

struct X
{
QString foo;
};

Can be memcpy-movable.

Thiago Macieira

unread,
Sep 21, 2014, 7:07:53 PM9/21/14
to std-dis...@isocpp.org
On Sunday 21 September 2014 20:03:25 Matheus Izvekov wrote:
> > I am saying that all standard layout types can be copied with user-defined
> > functions without triggering UB, since the whole point of standard layout
> > is
> > that they are layout-compatible with C types (PODs). Whether the copied
> > type
> > makes sense or not depends only on the user-defined copy/move constructors
> > and
> > assignment operators, and on the destructor.
>
> Well that could make sense, if it said anything about that in the standard,
> but I can't find it.

It doesn't. That was my conclusion based on what a standard layout type is.

Ville Voutilainen

unread,
Sep 21, 2014, 7:15:05 PM9/21/14
to std-dis...@isocpp.org
On 22 September 2014 02:07, Thiago Macieira <thi...@macieira.org> wrote:
> On Monday 22 September 2014 01:59:59 Ville Voutilainen wrote:
>> Well, that depends when you expect the UB to occur. Sure, it might not
>> occur on copy,
>> but
>>
>> struct X
>> {
>> std::string foo;
>> };
>>
>> is standard-layout, but not trivially copyable. If you want to memcpy
>> safely, you need
>> to have a trivially copyable type. PODs are both standard-layout and
>> trivial.
>
> Read what I said: it depends on the copy constructor of the type. X's copy
> constructor does not allow for it.

Which is why the language rules provide memcpy-guarantees for types for
which the language can provide them, aka trivially copyable types.
The language rules do not attempt to look at what a user-provided copy
constructor does
or does not do, they assume that a user-provided copy constructor does more than
bitwise copying.

>
> However:
>
> struct X
> {
> QString foo;
> };
>
> Can be memcpy-movable.

I don't quite see how. Trivial move operations do not do memcpy+clear.

Matheus Izvekov

unread,
Sep 21, 2014, 7:17:56 PM9/21/14
to std-dis...@isocpp.org
On Sunday, September 21, 2014 8:04:11 PM UTC-3, Ville Voutilainen wrote:
Where is the standard worded so?

N3797 Section 9:

A trivially copyable class is a class that:
— has no non-trivial copy constructors (12.8),
— has no non-trivial move constructors (12.8),
— has no non-trivial copy assignment operators (13.5.3, 12.8),
— has no non-trivial move assignment operators (13.5.3, 12.8), and
— has a trivial destructor (12.4).

I think the intention here is that a class with a deleted destructor does not have a trivial destructor, or else the last clause would have been worded like the ones above it.
 
I don't recall seeing a suggestion that a user could claim his class
to be trivially
copyable when it violates the language rules for trivially copyable types.

Well, my whole proposal idea on this thread can be boiled down to allowing a user to specify whether his class is trivially_copyable or not, unless the class
uses virtual members / inheritance and the implementation does not allow such classes to be copied (because for example they could store a pointer to something internal), in which case the class would
always be non-trivially copyable.

Ville Voutilainen

unread,
Sep 21, 2014, 7:22:02 PM9/21/14
to std-dis...@isocpp.org
On 22 September 2014 02:17, Matheus Izvekov <mizv...@gmail.com> wrote:
> On Sunday, September 21, 2014 8:04:11 PM UTC-3, Ville Voutilainen wrote:
>>
>> Where is the standard worded so?
>
>
> N3797 Section 9:
>
>> A trivially copyable class is a class that:
>> — has no non-trivial copy constructors (12.8),
>> — has no non-trivial move constructors (12.8),
>> — has no non-trivial copy assignment operators (13.5.3, 12.8),
>> — has no non-trivial move assignment operators (13.5.3, 12.8), and
>> — has a trivial destructor (12.4).
>
>
> I think the intention here is that a class with a deleted destructor does
> not have a trivial destructor, or else the last clause would have been
> worded like the ones above it.

I don't see how that would be the intention, since nothing in the specification
cross-referenced there (12.4) says anything about being deleted.

>
>>
>> I don't recall seeing a suggestion that a user could claim his class
>> to be trivially
>> copyable when it violates the language rules for trivially copyable types.
> Well, my whole proposal idea on this thread can be boiled down to allowing a
> user to specify whether his class is trivially_copyable or not, unless the
> class
> uses virtual members / inheritance and the implementation does not allow
> such classes to be copied (because for example they could store a pointer to
> something internal), in which case the class would
> always be non-trivially copyable.


What really strikes me as odd is that the example types in the first message in
this thread have user-provided copy constructors but you apparently don't care
whether they are invoked when objects of your class type are copied. I find it
very difficult to believe that's a case common enough to deserve core language
support.

Ville Voutilainen

unread,
Sep 21, 2014, 7:23:25 PM9/21/14
to std-dis...@isocpp.org
On 22 September 2014 02:22, Ville Voutilainen
<ville.vo...@gmail.com> wrote:
> On 22 September 2014 02:17, Matheus Izvekov <mizv...@gmail.com> wrote:
>> On Sunday, September 21, 2014 8:04:11 PM UTC-3, Ville Voutilainen wrote:
>>>
>>> Where is the standard worded so?
>>
>>
>> N3797 Section 9:
>>
>>> A trivially copyable class is a class that:
>>> — has no non-trivial copy constructors (12.8),
>>> — has no non-trivial move constructors (12.8),
>>> — has no non-trivial copy assignment operators (13.5.3, 12.8),
>>> — has no non-trivial move assignment operators (13.5.3, 12.8), and
>>> — has a trivial destructor (12.4).
>>
>>
>> I think the intention here is that a class with a deleted destructor does
>> not have a trivial destructor, or else the last clause would have been
>> worded like the ones above it.
>
> I don't see how that would be the intention, since nothing in the specification
> cross-referenced there (12.4) says anything about being deleted.


Also, there's another palatable reason why the wording for a destructor
is different - there can be only one. That's not the case for the others.

Matheus Izvekov

unread,
Sep 21, 2014, 7:31:30 PM9/21/14
to std-dis...@isocpp.org
On Sunday, September 21, 2014 8:22:02 PM UTC-3, Ville Voutilainen wrote:
I don't see how that would be the intention, since nothing in the specification
cross-referenced there (12.4) says anything about being deleted.

So why is it worded differently then?
Should a class ith deleted destructor be trivially copyable according to the current wording or not?
Is this something that would need an EWG issue to be resolved?

What really strikes me as odd is that the example types in the first message in
this thread have user-provided copy constructors but you apparently don't care
whether they are invoked when objects of your class type are copied. I find it
very difficult to believe that's a case common enough to deserve core language
support.

Yeah that was omitted for brevity.

An example satisfying your criticism would be:

template<class T> struct unaligned {
       
using type = T;
//      static_assert(std::is_trivially_copyable<type>{}, "");
       
template<typename... Args> unaligned(Args&&... args) {
               
const type tmp(std::forward<Args>(args)...);
                memcpy
(&base, &tmp, sizeof(type));
       
}
        unaligned
(const unaligned &a) { *this = a; }

        unaligned
& operator =(const unaligned &a) {
                memcpy
(&base, &a.base, sizeof(type));
               
return *this;
       
}
       
operator type() const {
                type ret
;
                memcpy
(&ret, &base, sizeof(type));
               
return ret;
       
}
private:
        type
base;
};

This is just a preliminary idea of what could be a wrapper for working with unaligned objects.
I am aware there are several other issues there.
The idea is that such a wrapper could be a companion for a proposal which standardized the gnu::packed attribute.

Ville Voutilainen

unread,
Sep 21, 2014, 7:43:45 PM9/21/14
to std-dis...@isocpp.org
On 22 September 2014 02:31, Matheus Izvekov <mizv...@gmail.com> wrote:
> On Sunday, September 21, 2014 8:22:02 PM UTC-3, Ville Voutilainen wrote:
>>
>> I don't see how that would be the intention, since nothing in the
>> specification
>> cross-referenced there (12.4) says anything about being deleted.
>
>
> So why is it worded differently then?

Because there's only ever one destructor, and there may be multiple overloads
of the other special member functions, as I wrote in a follow-up.

> Should a class ith deleted destructor be trivially copyable according to the
> current wording or not?

As far as I can see, yes, it should be trivially copyable but not
trivially copyconstructible.

> Is this something that would need an EWG issue to be resolved?

Probably.
Why don't you just leave out the actual copy operations and constrain
the templated one
that is not meant to be a copy so that it SFINAEs away when Args... is the same
type as the wrapper?

Matheus Izvekov

unread,
Sep 21, 2014, 8:02:51 PM9/21/14
to std-dis...@isocpp.org
On Sunday, September 21, 2014 8:43:45 PM UTC-3, Ville Voutilainen wrote:
Because there's only ever one destructor, and there may be multiple overloads
of the other special member functions, as I wrote in a follow-up.

Sure, that's one possible interpretation.
 
> Is this something that would need an EWG issue to be resolved?

Probably.

Well, is there a newbie friendly introduction to writing such an issue?
I wanted to give it a try, could be a useful skill.
 

Why don't you just leave out the actual copy operations and constrain
the templated one
that is not meant to be a copy so that it SFINAEs away when Args... is the same
type as the wrapper?

I understand what you mean by SFINAE out the templated one, I just didn't do it because I thought overload resolution would
always pick the non-templated copy constructor, and I thought it would make the example less clear if I did so.
But what do you mean by leaving out the actual copy operations?

They can't be left out if I understand you right, the object is possibly unaligned, you can only touch it with a 10 foot pole (memcpy).

Thiago Macieira

unread,
Sep 21, 2014, 8:47:48 PM9/21/14
to std-dis...@isocpp.org
On Monday 22 September 2014 02:15:04 Ville Voutilainen wrote:
> > struct X
> > {
> >
> > QString foo;
> >
> > };
> >
> > Can be memcpy-movable.
>
> I don't quite see how. Trivial move operations do not do memcpy+clear.

Sorry, to be specific: you can memcpy that X, provided that you discard the old
object without letting the destructor run.

Ville Voutilainen

unread,
Sep 22, 2014, 2:10:37 AM9/22/14
to std-dis...@isocpp.org
On 22 September 2014 03:02, Matheus Izvekov <mizv...@gmail.com> wrote:
>> > Is this something that would need an EWG issue to be resolved?
>> Probably.
> Well, is there a newbie friendly introduction to writing such an issue?
> I wanted to give it a try, could be a useful skill.

I don't know of such an introduction. Perhaps mimicking an existing
issue would work.

>> Why don't you just leave out the actual copy operations and constrain
>> the templated one
>> that is not meant to be a copy so that it SFINAEs away when Args... is the
>> same
>> type as the wrapper?
> I understand what you mean by SFINAE out the templated one, I just didn't do
> it because I thought overload resolution would
> always pick the non-templated copy constructor, and I thought it would make
> the example less clear if I did so.

If you have a non-const unaligned, the template will be used for copying.

> But what do you mean by leaving out the actual copy operations?
> They can't be left out if I understand you right, the object is possibly
> unaligned, you can only touch it with a 10 foot pole (memcpy).


I must be missing some parts of the picture, now. If you can't use the
implicitly-defaulted
copy operations, how can your class ever be trivially copyable? Is the
intention so
that you can somehow detect when the object is properly aligned and
use the default
special member functions in that case, and not otherwise? It seems we are rather
far away from a situation where relaxing the constraints for trivial
copyability would
be a solution.

David Krauss

unread,
Sep 22, 2014, 2:29:02 AM9/22/14
to std-dis...@isocpp.org
On 2014–09–22, at 2:10 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:

I must be missing some parts of the picture, now. If you can't use the
implicitly-defaulted
copy operations, how can your class ever be trivially copyable?

I’ve not been following so closely, but I recall an example wherein the class could *only* be copied by memcpy due to unalignment. The only safe copy-constructor is one that explicitly calls memcpy. The user wishes std::is_trivially_copyable to return true for the class. Of course, relying on the default copy operations would be unsafe because the implementation can (and probably will) generate code which relies on the actual alignment.

My gut instinct would be that a user-defined specialization of std::is_trivially_copyable should notify the standard library and any other introspection of what the user did. But this discussion is pretty far along and I’m missing a lot :P .

I don’t think we want two separate notions of core trivial copyability, one of which allows generation of something like memcpy and the other specifically requires calling memcpy itself.

Matheus Izvekov

unread,
Sep 22, 2014, 8:46:41 AM9/22/14
to std-dis...@isocpp.org
On Monday, September 22, 2014 3:10:37 AM UTC-3, Ville Voutilainen wrote:
If you have a non-const unaligned, the template will be used for copying.

Ah OK, but like I said, this was supposed to be a short example :-)
I will develop this further in the future.


> But what do you mean by leaving out the actual copy operations?
> They can't be left out if I understand you right, the object is possibly
> unaligned, you can only touch it with a 10 foot pole (memcpy).


I must be missing some parts of the picture, now. If you can't use the
implicitly-defaulted
copy operations, how can your class ever be trivially copyable? Is the
intention so
that you can somehow detect when the object is properly aligned and
use the default
special member functions in that case, and not otherwise? It seems we are rather
far away from a situation where relaxing the constraints for trivial
copyability would
be a solution.

The intention would be that all implicitly-defaulted copy operations are substituted by an explicit implementation
in terms of memcpy. There is no point in detecting if the object is already aligned outside of memcpy, since memcpy is already allowed to do that.

Those explicit implementations would have the same semantics as the implicit-defaulted ones, so the fact that the standard does not allow them to be trivially copyable
is only unfortunate.

One of the big uses of "unaligned" would be to create a packed struct with a bunch of members wrapped in unaligned, assign a value to every member, and then
fwrite the whole struct to a file. Or do the reverse operation, fread the whole struct from a file and then read each member.

But using fread/fwrite on the struct requires that it is trivially_copyable, which requires that every member is trivially_copyable.

Matheus Izvekov

unread,
Sep 22, 2014, 9:11:03 AM9/22/14
to std-dis...@isocpp.org
On Monday, September 22, 2014 3:29:02 AM UTC-3, David Krauss wrote:
I’ve not been following so closely, but I recall an example wherein the class could *only* be copied by memcpy due to unalignment. The only safe copy-constructor is one that explicitly calls memcpy. The user wishes std::is_trivially_copyable to return true for the class. Of course, relying on the default copy operations would be unsafe because the implementation can (and probably will) generate code which relies on the actual alignment.

Correct.
 

My gut instinct would be that a user-defined specialization of std::is_trivially_copyable should notify the standard library and any other introspection of what the user did. But this discussion is pretty far along and I’m missing a lot :P .

Yeah that would be general idea, but I discussed already why I don't think allowing the user to directly specialize std::is_trivially_copyable is a good idea.
The user would be able to force it to true for classes which use virtual members / virtual inheritance. The situation now is that it's allowed
to implement virtuals in a way that breaks memcpy'ability, for example by having an implicit member pointer that points to somewhere within itself. The copy of such
an object would have this member pointing to the original, and not to itself!

The most flexible way out of the situation would be for the standard to require that all classes that don't use virtuals to be memcpy'able, that is,
they would not be allowed to use any tricks which would break memcpy'ability, and then leave it as implementation defined if classes with virtuals
would be memcpy'able.

The "is trivially copyable" concept would be defined in slightly different terms.
A trivially copyable class would be one that occupies contiguous bytes of storage, does not use any implementation tricks that would break memcpy'ability (like pointers to internals),
and additionally whose copy semantics as defined by the copy/move constructors/operators are like the implicit ones.

The last point above is the one where the compiler has trouble currently. The current rules assume that only the implicit operators have those semantics, and the only way out of this situation
would be to allow the user to specify that somehow, there is no way to make compilers "detect" if this where the case by parsing the user implementation.

Ville Voutilainen

unread,
Sep 22, 2014, 10:01:07 AM9/22/14
to std-dis...@isocpp.org
On 22 September 2014 15:46, Matheus Izvekov <mizv...@gmail.com> wrote:
>> > But what do you mean by leaving out the actual copy operations?
>> > They can't be left out if I understand you right, the object is possibly
>> > unaligned, you can only touch it with a 10 foot pole (memcpy).
>> I must be missing some parts of the picture, now. If you can't use the
>> implicitly-defaulted
>> copy operations, how can your class ever be trivially copyable? Is the
>> intention so
>> that you can somehow detect when the object is properly aligned and
>> use the default
>> special member functions in that case, and not otherwise? It seems we are
>> rather
>> far away from a situation where relaxing the constraints for trivial
>> copyability would
>> be a solution.
> The intention would be that all implicitly-defaulted copy operations are
> substituted by an explicit implementation
> in terms of memcpy. There is no point in detecting if the object is already
> aligned outside of memcpy, since memcpy is already allowed to do that.
> Those explicit implementations would have the same semantics as the
> implicit-defaulted ones, so the fact that the standard does not allow them
> to be trivially copyable
> is only unfortunate.

I must admit I don't follow. In your previous message you say that the
copy operations
can't be left out because you need to make sure they use memcpy and apparently
the implicitly-defaulted ones won't do. Now you're saying that they
supposedly have
the same semantics as the implicitly-defaulted ones. Which is it?

Matheus Izvekov

unread,
Sep 22, 2014, 10:17:36 AM9/22/14
to std-dis...@isocpp.org
On Monday, September 22, 2014 11:01:07 AM UTC-3, Ville Voutilainen wrote:
I must admit I don't follow. In your previous message you say that the
copy operations
can't be left out because you need to make sure they use memcpy and apparently
the implicitly-defaulted ones won't do. Now you're saying that they
supposedly have
the same semantics as the implicitly-defaulted ones. Which is it?

In the specific case of the unaligned wrapper, the implicit-default ones won't do because they can assume that the object is aligned.
So you have to substitute all the implicit ones by explicit ones implemented with memcpy.

But those explicit ones still have the same semantics as the implicit ones, ie they copy all members individually.
So such an explicit copy constructor/operator can still be substituted by a plain memcpy by the user, which is in fact what the explicit operators are doing anyway.

That's why such a class would still be trivially copyable if the rules allowed it.

Ville Voutilainen

unread,
Sep 22, 2014, 10:39:08 AM9/22/14
to std-dis...@isocpp.org
On 22 September 2014 17:17, Matheus Izvekov <mizv...@gmail.com> wrote:
> In the specific case of the unaligned wrapper, the implicit-default ones
> won't do because they can assume that the object is aligned.
> So you have to substitute all the implicit ones by explicit ones implemented
> with memcpy.
> But those explicit ones still have the same semantics as the implicit ones,
> ie they copy all members individually.

Yes, but that's as far as the superficial similarity of the semantics
goes. The semantics
are not the same, really.

> So such an explicit copy constructor/operator can still be substituted by a
> plain memcpy by the user, which is in fact what the explicit operators are
> doing anyway.
> That's why such a class would still be trivially copyable if the rules
> allowed it.

That sounds like such cases should have a way to state that their user-provided
copy operations are almost-like-trivial, and UB is the result if
substituting them with
memcpy isn't right. In other words, these things are very different
from the existing
rules, in the sense that it's a request to make promises to users that
might not be
true, and hence such things would pass through compile-time checks but may
cause UB if the promises to such checks "lie".

The UB can, of course, be avoided if instead of allowing user-provided
copy operations
to be trivial we invent some syntax that says "default using memcpy".
Or probably
more generally, "default using bit-blasting that doesn't require
proper alignment".

Matheus Izvekov

unread,
Sep 22, 2014, 10:57:20 AM9/22/14
to std-dis...@isocpp.org
On Monday, September 22, 2014 11:39:08 AM UTC-3, Ville Voutilainen wrote:
Yes, but that's as far as the superficial similarity of the semantics
goes. The semantics
are not the same, really.

Yes that is true, I understand what you mean. is_trivially_copyable wwould still be false in those cases.
 
That sounds like such cases should have a way to state that their user-provided
copy operations are almost-like-trivial, and UB is the result if
substituting them with
memcpy isn't right. In other words, these things are very different
from the existing
rules, in the sense that it's a request to make promises to users that
might not be
true, and hence such things would pass through compile-time checks but may
cause UB if the promises to such checks "lie".


Yes, that's another alternative. Myriachan had suggested in another thread proposing an attribute which would mark
a user provided constructor/operator as trivial. But I am not sure how the informal rules currently stand in regards to attributes.
I remember from discussions a while ago that the committee would likely reject standardizing any attributes that changed the language semantics,
and it would be a sort of a battle to convince them of just opening the gates there.
Is that still the case?
How far can attributes go in modifying the semantics?
I suppose modifying triviality would be ok, but modifying overload resolution would perhaps be too far there.
 
The UB can, of course, be avoided if instead of allowing user-provided
copy operations
to be trivial we invent some syntax that says "default using memcpy".
Or probably
more generally, "default using bit-blasting that doesn't require
proper alignment".

Yes that is another possibility, proposing another attribute [[unaligned]] which would make implementations take care of unaligned accesses by themselves.
The visual c++ compiler has something like this, __unaligned, but it acts like another type qualifier and affects overload resolution just like const and volatile does.
Perhaps a plain [[unaligned]] which just takes care of the accesses but does not affect overload resolution would be an acceptable compromise.

Ville Voutilainen

unread,
Sep 22, 2014, 11:02:08 AM9/22/14
to std-dis...@isocpp.org
On 22 September 2014 17:57, Matheus Izvekov <mizv...@gmail.com> wrote:
>> That sounds like such cases should have a way to state that their
>> user-provided
>> copy operations are almost-like-trivial, and UB is the result if
>> substituting them with
>> memcpy isn't right. In other words, these things are very different
>> from the existing
>> rules, in the sense that it's a request to make promises to users that
>> might not be
>> true, and hence such things would pass through compile-time checks but may
>> cause UB if the promises to such checks "lie".
> Yes, that's another alternative. Myriachan had suggested in another thread
> proposing an attribute which would mark
> a user provided constructor/operator as trivial. But I am not sure how the
> informal rules currently stand in regards to attributes.

The general idea is that attributes should not have a semantic effect,
and we're either
at or across the borderline of what is or is not a semantic effect
here, I think. I would
be very hesitant to make something like that an attribute.

> I remember from discussions a while ago that the committee would likely
> reject standardizing any attributes that changed the language semantics,
> and it would be a sort of a battle to convince them of just opening the
> gates there.
> Is that still the case?

Yes. :)

> How far can attributes go in modifying the semantics?

As short a distance as possible, preferably nowhere at all.

>> The UB can, of course, be avoided if instead of allowing user-provided
>> copy operations
>> to be trivial we invent some syntax that says "default using memcpy".
>> Or probably
>> more generally, "default using bit-blasting that doesn't require
>> proper alignment".
> Yes that is another possibility, proposing another attribute [[unaligned]]
> which would make implementations take care of unaligned accesses by
> themselves.

Well, perhaps not an attribute, but indeed have more stringent requirements
on what the implementation can do and what it must do, but to avoid saying
that it necessarily does memcpy specifically.

I guess the idea in general is worth further exploration, in whatever form.

Matheus Izvekov

unread,
Sep 22, 2014, 11:55:32 AM9/22/14
to std-dis...@isocpp.org
On Monday, September 22, 2014 12:02:08 PM UTC-3, Ville Voutilainen wrote:
The general idea is that attributes should not have a semantic effect,
and we're either
at or across the borderline of what is or is not a semantic effect
here, I think. I would
be very hesitant to make something like that an attribute.

Well, I think it deserves a try at least, this would be the most useful addition so far.
I understand the Qt and Facebook guys/gals would be interested in a way to have trivially
copyable classes with non implicit-default operators, so this idea would have some extra support I guess.

Syntactically I think it could go something like:
struct foo {
    foo
(const foo &a) [[trivial]] {
       
// some core here that satisfies the semantics of
       
// the implicit-default copy constructor
   
}
};

Though not sure that's the exact position the attribute should go.
 
Yes. :)

Though I was surprised when I learned that clang and gcc already allow all their __attribute__ stuff to be specified with [[gnu::whatever]]
I thought that rule would have extended to cover vendor specific attributes. Are gcc and clang non-conforming here?
 
Well, perhaps not an attribute, but indeed have more stringent requirements
on what the implementation can do and what it must do, but to avoid saying
that it necessarily does memcpy specifically.

I guess the idea in general is worth further exploration, in whatever form.

Yes sure, we would just say that an object with [[unaligned]] attribute would possibly be unaligned, so they have to take care of that possibility.
Compiler writers are smart guys so they know what they must do here :)

[[unaligned]] in general would be much more flexible than the unaligned wrapper. Specifically we don't have to say anything about trivial copyability at all.
This would allow it to work with volatile qualified objects for example, since the semantics there are just about not optimizing away accesses at all. Some implementations would
have to split an unaligned access into multiple accesses and thus make them non-atomic, but this is fine, that's what std::atomic is for anyway.

[[unaligned]] in general could be made in a way that the language semantics is not changed at all, but it would be useful if it could make one small change: make the object be naturally aligned at 1.
This way you could combine [[unaligned]] with alignas(X) so that you can sub-align an object to somewhere less than natural align, but more than 1.
Something like:
[[unaligned]] alignas(2) int bar;

The other attribute that would be useful together with unaligned would be [[packed]], similar to gnu::packed, but it could also make the class itself and all members [[unaligned]]

[[packed]] has one small semantic effect however, it changes the size of the struct and the natural aligment of itself and it's members, of course.

The other alternative to attributes would be to add contextual keywords for all of those: trivial, unaligned, and packed. But preferably not those but keywords less likely to clash with existing code.

Ville Voutilainen

unread,
Sep 22, 2014, 12:07:15 PM9/22/14
to std-dis...@isocpp.org
On 22 September 2014 18:55, Matheus Izvekov <mizv...@gmail.com> wrote:
> Though I was surprised when I learned that clang and gcc already allow all
> their __attribute__ stuff to be specified with [[gnu::whatever]]
> I thought that rule would have extended to cover vendor specific attributes.
> Are gcc and clang non-conforming here?

No. Those attributes have an attribute-namespace, and such vendor attributes
are allowed to have whatever effect the vendor wishes them to have.

>> I guess the idea in general is worth further exploration, in whatever
>> form.
> The other alternative to attributes would be to add contextual keywords for
> all of those: trivial, unaligned, and packed. But preferably not those but
> keywords less likely to clash with existing code.

Well, clashes are less likely if you put such contextual keywords into suitable
locations. 'final' in class-head is an example of that, although it
did require small
adjustments to fit into the rest of the grammar.

Matheus Izvekov

unread,
Sep 22, 2014, 12:18:49 PM9/22/14
to std-dis...@isocpp.org
On Monday, September 22, 2014 1:07:15 PM UTC-3, Ville Voutilainen wrote:
Well, clashes are less likely if you put such contextual keywords into suitable
locations. 'final' in class-head is an example of that, although it
did require small
adjustments to fit into the rest of the grammar.

Well, how about:

struct foo {
    foo
(const foo &a) = trivial {

       
// some core here that satisfies the semantics of
       
// the implicit-default copy constructor
   
}
};

And:

alignas(unaligned, 2) int bar;

And additionally, perhaps [[packed]] could be done away entirely by just using unaligned, with the additional rule that an unaligned struct has all it's members / bases implicitly unaligned.

So:

struct alignas(unaligned) foo {
   
int bar; //same as alignas(unaligned) int bar;
};

Ok, this is starting to look good =)


Matheus Izvekov

unread,
Sep 22, 2014, 12:23:11 PM9/22/14
to std-dis...@isocpp.org
On Monday, September 22, 2014 1:18:49 PM UTC-3, Matheus Izvekov wrote:
struct alignas(unaligned) foo {
   
int bar; //same as alignas(unaligned) int bar;
};


Oops small brainfart there, completely forgot alignas accepts a constexpr, and not just a numeric literal.

Matheus Izvekov

unread,
Sep 22, 2014, 12:49:58 PM9/22/14
to std-dis...@isocpp.org
Well, I suppose another way would be to allow a negative value as an alignas argument.

Something like:
alignas(-2) int bar;

And so that alignof(bar) == std::size_t(2)
One would detect that a type / object is unaligned by comparing that to the natural alignment of the base type.

Also:

struct alignas(-1) foo {
   
int bar; //same as alignas(-1) int bar;
};

And:

struct alignas(-2) foo {
   
int bar; //same as alignas(-2) int bar;
};

But:

struct alignas(-8) foo {
   
int bar; // no change here, in case alignof(int) < 8
};


Ville Voutilainen

unread,
Sep 22, 2014, 1:41:23 PM9/22/14
to std-dis...@isocpp.org
On 22 September 2014 19:18, Matheus Izvekov <mizv...@gmail.com> wrote:
> Well, how about:
>
> struct foo {
> foo(const foo &a) = trivial {
> // some core here that satisfies the semantics of
> // the implicit-default copy constructor
> }
> };

I would certainly prefer a solution where that "some code" isn't written at all,
but rather either the class or just its copy operation is
syntactically marked to
do the right thing.

Matheus Izvekov

unread,
Sep 22, 2014, 2:11:06 PM9/22/14
to std-dis...@isocpp.org
On Monday, September 22, 2014 2:41:23 PM UTC-3, Ville Voutilainen wrote:
I would certainly prefer a solution where that "some code" isn't written at all,
but rather either the class or just its copy operation is
syntactically marked to
do the right thing.

That's though...
The idea here is to allow a catch all way for a class author to perform a copy in a different way than what the compiler would have done for the implicit-default copy constructor,
provided it's in a way that it works "as-if" the implicit-default one was used in situations where that would have been well-defined.

It could also allow expressions that would have side effect, provided the programmer knows that he is doing.
It could be for example generating profiling / debug aid events, like submitting a log or incrementing a global counter.
This would be certainly dangerous of course, the programmer always gets to keep the pieces.

Myriachan

unread,
Sep 22, 2014, 5:03:42 PM9/22/14
to std-dis...@isocpp.org
Here is how I would define things, after a discussion in a std-proposals thread:

1. All most-derived types occupy a contiguous array of bytes.
1a. Implementations could still do things like allocate memory elsewhere for virtual function management if they wanted to.
1b. This requirement does not hold for subobjects; in particular, they can overlap noncontiguously with their virtual bases.  However, the subobject rule is still subject to the definition of memcpy-compatible: a subobject that is memcpy-compatible would still have to be memcpy-compatible.

2. A memcpy-compatible class is one in which no class in the hierarchy has a virtual base or virtual functions.
2a. std::memcpy (*1) is well-defined for memcpy-compatible classes as recursive memberwise copying, even if the classes are not also trivially-copyable.
2b. std::memcpy on a memcpy-compatible but non-trivially-copyable class is well-defined, but may still end up resulting in undefined behavior if the fact that constructors and destructors are not invoked is an issue.  (std::string is memcpy-compatible by this definition, and is possibly even standard-layout, but copying one with std::memcpy would be a really bad idea.)
2c. The layout of a memcpy-compatible class is fixed at compile time.
2d. As a consequence of 2a and 2c, offsetof(C, m) is well-defined for memcpy-compatible classes C and members mm can also be a member of a base class, a member of a member, an element of an array member, or recursively thereof.  However, when m is of reference type, though offsetof(C, m) is well-defined in an abstract sense, it is ill-formed for practical reasons.  No diagnostic is required; should none be issued, and offsetof(C, m) is evaluated, the behavior is undefined.
2e. The order of class members and of base class subobjects in memory have no additional constraints versus C++14: offsetof is really only useful for finding a single element when used on a class that is not standard-layout.
2f. The quality of being memcpy-compatible is represented in the type trait std::is_memcpy_compatible.
2g. An implementation may be able to safely memcpy classes with virtual inheritance, virtual functions, or both, or may be able to under certain conditions; the implementation can indicate this by making std::is_memcpy_compatible<C>::value be true for class C.
2h. TODO: Evaluate 2g in terms of its effects on base class subobjects.  Should memcpy be undefined on base class subobjects with virtual functions regardless of 2g?
2i. As an exception, memcpy on an empty base class subobject that has been elided to size zero is undefined.  (Perhaps we should have a trait for detecting this case?)

3. A trivially-copyable class is a memcpy-compatible class in which no class in the hierarchy has a nontrivial copy constructor, nontrivial move constructor, nontrivial copy assignment operator or nontrivial move assignment operator, and in which every class has a trivial destructor.
3a. TODO: We need some way to set this flag on for memcpy-compatible classes that do not meet these criteria.

4. A standard-layout class is a memcpy-compatible class in which only one class in the entire class hierarchy is nonempty, and a situation in which an empty base class got shoved out of the way of another copy of that class to avoid the unique address rule did not occur.

5. One more rule I think would be worth changing relates to the fixed layout you get with memcpy-compatible classes.  If a memcpy-compatible class derives from exactly one nonempty base class and any number of empty base classes (that don't trip the unique address rule), has no non-static data members of its own, and defines no virtual functions other than ones that use or could use the override contextual keyword, then if you have an array of objects of the derived class, it's safe to use pointer arithmetic with a pointer to the nonempty base class.
5a. This is for allowing extending existing classes more easily, especially C classes with standard-layout.


Note 1 from memcpy-compatible: This applies not just to std::memcpy itself but to reinterpret_casting to unsigned char * the object's pointer yourself and copying the bytes.  It also applies to copying the bytes to an unsigned char array.  These bytes can be mangled in any way desired; so long as they end up in their original values in their original order when they go back to an object of the original type, it's well-defined.

Melissa

Thiago Macieira

unread,
Sep 23, 2014, 3:58:34 PM9/23/14
to std-dis...@isocpp.org
On Monday 22 September 2014 14:03:41 Myriachan wrote:
> 1b. This requirement does not hold for subobjects; in particular, they can
> overlap noncontiguously with their virtual bases. However, the subobject
> rule is still subject to the definition of *memcpy-compatible*: a subobject
> that is *memcpy-compatible* would still have to be *memcpy-compatible*.
>
> 2. A *memcpy-compatible* class is one in which no class in the hierarchy
> has a virtual base or virtual functions.

And whose members are all of the same protection level. In other words, the
class is standard-layout.

An implementation is allowed to turn a type X that has multiple protection
levels to be (exercising your 1b above):

struct X_layout {
struct X_protecteds {
/* protected members go here */
} *protecteds;
struct X_private {
/* private members go here */
} *privates;
/* public members go here */
};

sizeof(X) is the sum of all three structs and the compiler manages the two
hidden pointers. That way, when X is the most derived type, it still occupies
a contiguous array of bytes, but it's not relocatable. What's worse, a Y
deriving from X could make X's sub-object non-contiguous.

> 2b. std::memcpy on a *memcpy-compatible* but non-*trivially-copyable* class
> is well-defined, but may still end up resulting in undefined behavior if
> the fact that constructors and destructors are not invoked is an issue. (
> std::string is *memcpy-compatible* by this definition, and is possibly even
> *standard-layout*, but copying one with std::memcpy would be a really bad
> idea.)

That's the difference between memcpy-copying and memcpy-moving.

The standard talks about memcpying away and then back. That would work for
this class above. The standard does not talk about memcpy-based relocation,
only about copying back to where it was.

> 2e. The order of class members and of base class subobjects in memory have
> no additional constraints versus C++14: offsetof is really only useful for
> finding a single element when used on a class that is not *standard-layout*.

I disagree. The requirement should be that the class is standard-layout in the
first place. Class X above could not use offsetof for any private or protected
member.

Alternatively, we add a new trait that indicates whether a type is
offsetof'able and memcpy'able, and at least all standard layout types must be
included in this new trait, but an implementation may include others too.

> 2f. The quality of being *memcpy-compatible* is represented in the type
> trait std::is_memcpy_compatible.
> 2g. An implementation may be able to safely memcpy classes with virtual
> inheritance, virtual functions, or both, or may be able to under certain
> conditions; the implementation can indicate this by making
> std::is_memcpy_compatible<C>::value be true for class C.

Such as this trait.

> 2i. As an exception, memcpy on an empty base class subobject that has been
> elided to size zero is undefined. (Perhaps we should have a trait for
> detecting this case?)

Suggestion: std::is_empty_aggregate.

> 3. A *trivially-copyable* class is a *memcpy-compatible* class in which no
> class in the hierarchy has a nontrivial copy constructor, nontrivial move
> constructor, nontrivial copy assignment operator or nontrivial move
> assignment operator, and in which every class has a trivial destructor.
> 3a. *TODO:* We need some way to set this flag on for *memcpy-compatible*
> classes that do not meet these criteria.

Allow the user overriding std::is_memcpy_compatible. And please split the
trait in two: memcpy copying and memcpy moving.

> 5. One more rule I think would be worth changing relates to the fixed
> layout you get with *memcpy-compatible* classes. If a *memcpy-compatible*
> class derives from exactly one nonempty base class and any number of empty
> base classes (that don't trip the unique address rule), has no non-static
> data members of its own, and defines no virtual functions other than ones
> that use or could use the override contextual keyword, then if you have an
> array of objects of the derived class, it's safe to use pointer arithmetic
> with a pointer to the nonempty base class.

This is ABI-dependent because you included virtuals. I'd rather not go there.

Case in point: you did not exclude virtuals from those other, empty bases.
Therefore, they are not actually empty, since they must have at least the
vtable pointer(s) present. In the portable IA-64 C++ ABI, overriding a virtual
from a non-primary base is equivalent to adding a new virtual. Covariant
overrides are also the same as new virtuals.

So no, don't go there.

Matheus Izvekov

unread,
Sep 24, 2014, 10:36:07 AM9/24/14
to std-dis...@isocpp.org
On Tuesday, September 23, 2014 4:58:34 PM UTC-3, Thiago Macieira wrote:
That's the difference between memcpy-copying and memcpy-moving.

The standard talks about memcpying away and then back. That would work for
this class above. The standard does not talk about memcpy-based relocation,
only about copying back to where it was.

It does talk about "memcpy-moving".

N3797 [basic.types] paragraph 3

For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2, where
neither obj1 nor obj2 is a base-class subobject, if the underlying bytes (1.7) making up obj1 are copied
into obj2, 43 obj2 shall subsequently hold the same value as obj1. [Example:
T* t1p;
T* t2p;
// provided that t2p points to an initialized object ...
std::memcpy(t1p, t2p, sizeof(T));
// at this point, every subobject of trivially copyable type in *t1p contains
// the same value as the corresponding subobject in *t2p

Myriachan

unread,
Sep 24, 2014, 6:00:36 PM9/24/14
to std-dis...@isocpp.org
On Tuesday, September 23, 2014 12:58:34 PM UTC-7, Thiago Macieira wrote:
On Monday 22 September 2014 14:03:41 Myriachan wrote:
> 1b. This requirement does not hold for subobjects; in particular, they can
> overlap noncontiguously with their virtual bases.  However, the subobject
> rule is still subject to the definition of *memcpy-compatible*: a subobject
> that is *memcpy-compatible* would still have to be *memcpy-compatible*.
>
> 2. A *memcpy-compatible* class is one in which no class in the hierarchy
> has a virtual base or virtual functions.

And whose members are all of the same protection level. In other words, the
class is standard-layout.

An implementation is allowed to turn a type X that has multiple protection
levels to be (exercising your 1b above):

struct X_layout {
        struct X_protecteds {
                /* protected members go here */
        } *protecteds;
        struct X_private {
                /* private members go here */
        } *privates;
        /* public members go here */
};

sizeof(X) is the sum of all three structs and the compiler manages the two
hidden pointers. That way, when X is the most derived type, it still occupies
a contiguous array of bytes, but it's not relocatable. What's worse, a Y
deriving from X could make X's sub-object non-contiguous.


The way I designed things, an implementation wouldn't be allowed to implement memcpy-compatible classes that way, so it wouldn't break.  My rule 2b would require that memcpy-compatible classes are able to be memcpy'd.  There are existing implementations that coalesce public/protected/private members together, though I don't know what they are.  Are there any existing implementations that implement such coalescing with pointers?

My rule 1b was referring to base class subobjects, but more importantly, the general rules get overridden by the more-specific for stricter sets of classes.

> 2b. std::memcpy on a *memcpy-compatible* but non-*trivially-copyable* class
> is well-defined, but may still end up resulting in undefined behavior if
> the fact that constructors and destructors are not invoked is an issue.  (
> std::string is *memcpy-compatible* by this definition, and is possibly even
> *standard-layout*, but copying one with std::memcpy would be a really bad
> idea.)

That's the difference between memcpy-copying and memcpy-moving.

The standard talks about memcpying away and then back. That would work for
this class above. The standard does not talk about memcpy-based relocation,
only about copying back to where it was.

That's not really about copying versus moving.  It would not be safe to memcpy-move a class that stores a pointer back into itself (as in class X, if instead of being compiler-generated it were written by the programmer that way), but memcpy in terms of memberwise copying may still be defined for that class (with my definition, if the class is memcpy-compatible).
 
> 2e. The order of class members and of base class subobjects in memory have
> no additional constraints versus C++14: offsetof is really only useful for
> finding a single element when used on a class that is not *standard-layout*.

I disagree. The requirement should be that the class is standard-layout in the
first place. Class X above could not use offsetof for any private or protected
member.


Definitely true if your class X is allowed to be implemented as you showed.
 
Alternatively, we add a new trait that indicates whether a type is
offsetof'able and memcpy'able, and at least all standard layout types must be
included in this new trait, but an implementation may include others too.
 
> 2f. The quality of being *memcpy-compatible* is represented in the type
> trait std::is_memcpy_compatible.
> 2g. An implementation may be able to safely memcpy classes with virtual
> inheritance, virtual functions, or both, or may be able to under certain
> conditions; the implementation can indicate this by making
> std::is_memcpy_compatible<C>::value be true for class C.

Such as this trait.

> 2i. As an exception, memcpy on an empty base class subobject that has been
> elided to size zero is undefined.  (Perhaps we should have a trait for
> detecting this case?)

Suggestion: std::is_empty_aggregate.


Mhmm, sounds about right.  How would the path through the class hierarchy be specified?  I suppose that if the way you get a pointer to the base class subobject is:

MostDerived *derived = ???;
Base *base = static_cast<MostDerived::IntermediateBase2::IntermediateBase1::...::Base *>(derived);

Then the trait is accessed with:

std::is_empty_aggregate<Base, IntermediateBase1, IntermediateBase2, MostDerived>::value


> 3. A *trivially-copyable* class is a *memcpy-compatible* class in which no
> class in the hierarchy has a nontrivial copy constructor, nontrivial move
> constructor, nontrivial copy assignment operator or nontrivial move
> assignment operator, and in which every class has a trivial destructor.
> 3a. *TODO:* We need some way to set this flag on for *memcpy-compatible*
> classes that do not meet these criteria.

Allow the user overriding std::is_memcpy_compatible. And please split the
trait in two: memcpy copying and memcpy moving.

> 5. One more rule I think would be worth changing relates to the fixed
> layout you get with *memcpy-compatible* classes.  If a *memcpy-compatible*
> class derives from exactly one nonempty base class and any number of empty
> base classes (that don't trip the unique address rule), has no non-static
> data members of its own, and defines no virtual functions other than ones
> that use or could use the override contextual keyword, then if you have an
> array of objects of the derived class, it's safe to use pointer arithmetic
> with a pointer to the nonempty base class.

This is ABI-dependent because you included virtuals. I'd rather not go there.

Case in point: you did not exclude virtuals from those other, empty bases.
Therefore, they are not actually empty, since they must have at least the
vtable pointer(s) present. In the portable IA-64 C++ ABI, overriding a virtual
from a non-primary base is equivalent to adding a new virtual. Covariant
overrides are also the same as new virtuals.

So no, don't go there.


You're right; my #5 is broken.  I missed an important phrase that I my mind intended but I didn't specify: standard-layout.  The empty base classes must be ones that get elided to zero size.  If they have virtual functions, this won't happen.  In all the ABIs I know of, what you're saying is true.

This is my revised #5.  In addition to adding standard-layout, I deleted the parentheses around the empty base class note about the unique address rule.

5. One more rule I think would be worth changing relates to the fixed layout you get with memcpy-compatible classes.  If a memcpy-compatible class derives from exactly one nonempty base class and any number of standard-layout empty base classes that don't trip the unique address rule, has no non-static data members of its own, and defines no virtual functions other than ones that use or could use the override contextual keyword, then if you have an array of objects of the derived class, it's safe to use pointer arithmetic with a pointer to the nonempty base class.

Thiago Macieira

unread,
Sep 24, 2014, 7:47:28 PM9/24/14
to std-dis...@isocpp.org, Myriachan
On Wednesday 24 September 2014 15:00:35 Myriachan wrote:
> > sizeof(X) is the sum of all three structs and the compiler manages the two
> > hidden pointers. That way, when X is the most derived type, it still
> > occupies
> > a contiguous array of bytes, but it's not relocatable. What's worse, a Y
> > deriving from X could make X's sub-object non-contiguous.
>
> The way I designed things, an implementation wouldn't be allowed to
> implement *memcpy-compatible* classes that way, so it wouldn't break. My
> rule 2b would require that *memcpy-compatible* classes are able to be
> memcpy'd. There are existing implementations that coalesce
> public/protected/private members together, though I don't know what they
> are. Are there any existing implementations that implement such coalescing
> with pointers?

I think you're putting the cart ahead of the horse.

We already have the class of types that are contiguous: standard layout. We
just need a way to restrict the group of all standard layout types to all
those that can be memcpy-relocated and those that can be memcpy-cloned.

> > I disagree. The requirement should be that the class is standard-layout in
> > the
> > first place. Class X above could not use offsetof for any private or
> > protected
> > member.
>
> Definitely true if your class X is allowed to be implemented as you showed.

So it just goes to show that the ability to use offsetof is the same as
standard layout.

> > Suggestion: std::is_empty_aggregate.
>
> Mhmm, sounds about right. How would the path through the class hierarchy
> be specified? I suppose that if the way you get a pointer to the base
> class subobject is:
>
> MostDerived *derived = ???;
> Base *base = static_cast<MostDerived::IntermediateBase2::IntermediateBase1
>
> ::...::Base *>(derived);
>
> Then the trait is accessed with:
>
> std::is_empty_aggregate<Base, IntermediateBase1, IntermediateBase2,
> MostDerived>::value

I don't see why we need the path. If std::is_empty_aggregate<Base>::value is
true, you should never attempt to copy it, whether it be sub-object of a
larger object or full object. If it's a full object, it contains one byte of
unspecified value that mustn't be read from or written to.

> > This is ABI-dependent because you included virtuals. I'd rather not go
> > there.
> >
> > Case in point: you did not exclude virtuals from those other, empty bases.
> > Therefore, they are not actually empty, since they must have at least the
> > vtable pointer(s) present. In the portable IA-64 C++ ABI, overriding a
> > virtual
> > from a non-primary base is equivalent to adding a new virtual. Covariant
> > overrides are also the same as new virtuals.
> >
> > So no, don't go there.
>
> You're right; my #5 is broken. I missed an important phrase that I my mind
> intended but I didn't specify: *standard-layout*. The empty base classes
> must be ones that get elided to zero size. If they have virtual functions,
> this won't happen. In all the ABIs I know of, what you're saying is true.
>
> This is my revised #5. In addition to adding *standard-layout*, I deleted
> the parentheses around the empty base class note about the unique address
> rule.
>
> 5. One more rule I think would be worth changing relates to the fixed
> layout you get with *memcpy-compatible* classes. If a *memcpy-compatible*
> class derives from exactly one nonempty base class and any number of
> *standard-layout* empty base classes that don't trip the unique address
> rule, has no non-static data members of its own, and defines no virtual
> functions other than ones that use or could use the override contextual
> keyword, then if you have an array of objects of the derived class, it's
> safe to use pointer arithmetic with a pointer to the nonempty base class.
> 5a. This is for allowing extending existing classes more easily, especially
> C classes with *standard-layout*.

Why do we need a definition for memcpy-compatible-layout to be different from
that of standard layout in the first place? Are we trying to capture the case
of non-empty deriving from non-empty? Those aren't standard layout but under
most ABIs they could be memcpy'ed whether they are sub-objects or full
objects. In fact, they are trivially copyable.

If that's the use-case you're aiming for, I have to ask first why standard
layout was restricted to only one non-empty class in the hierarchy. There must
have been a reason why C++11 gave it to us like that and that might be a
limitation on the ability to memcpy a full object.

BTW, I think we have now four traits to specify:

- s_empty_aggregate (because sizeof(X) can't be trusted)
- is_contiguous (something like your definition above, with better name)
those two are mutually exclusive
- is_memcpy_relocatable
- is_memcpy_clonable

The latter two are true for all trivially copyable types and false for others,
but may be overridden by the user to indicate that their type is relocatable
or clonable.

Myriachan

unread,
Sep 25, 2014, 3:13:15 AM9/25/14
to std-dis...@isocpp.org
On Wednesday, September 24, 2014 4:47:28 PM UTC-7, Thiago Macieira wrote:
I think you're putting the cart ahead of the horse.

We already have the class of types that are contiguous: standard layout. We
just need a way to restrict the group of all standard layout types to all
those that can be memcpy-relocated and those that can be memcpy-cloned.


Oh, are you saying to extend the definition of standard-layout to include classes that would currently be trivially-copyable but not standard-layout?  For example, classes with non-static members in multiple levels of the hierarchy, or mixed member or base class access-specifiers.  If it's called standard-layout instead of memcpy-compatible, I would be fine with that; I just care about the concepts.

I see things a bit differently: I see things in terms of what is true of the implementation, versus what is true of the program as written.  The implementation has certain requirements: it is not possible for a class using virtual inheritance to be safe to copy with memcpy, for example, in most if not all implementations.  std::string is safe to memcpy as far as the compiler is concerned; it's often just a pointer and a couple of std::size_ts.  It's only a bad idea to memcpy them because std::basic_string<...> has a copy constructor and a destructor.  After memcpying a std::string, if you magically could access the std::string's private variables, they would be correctly copied, rather than have undefined behavior.  It is only when methods are called that undefined behavior could occur, and that's due to the code as written, not the compiler.

My point is that the existence of a non-trivial copy constructor, move constructor, copy assignment operator, move assignment operator, or destructor should give the compiler permission to go crazy with the class layout.  You should still be able to memcpy such a class if you don't care about those special member functions not being called.  In fact, the likely reason that you wouldn't care about them is that you're part of one of those classes, and you've coded your class to be safe with memcpy.  A conditional std::memcpy(this, &copy, sizeof(*this)); in a copy constructor or a std::memset(this, 0, sizeof(*this)); in a default constructor are common such use cases now, even though they are technically illegal.

By the way, from your quoting of my message, I see that some of my text has asterisks.  When I write my messages, these words were entered as italics, so I suppose this formatting is lost for some recipients of this list.  I intended them to mean to refer to keywords in the Standard, or made-up keywords that could be, not as a form of emphasis.  Likewise, language keywords and code are in monospace, which may not show at all.  I didn't intend the italics to be a form of emphasis; I'm sorry if it comes across that way.
 
> > I disagree. The requirement should be that the class is standard-layout in
> > the
> > first place. Class X above could not use offsetof for any private or
> > protected
> > member.
>
> Definitely true if your class X is allowed to be implemented as you showed.

So it just goes to show that the ability to use offsetof is the same as
standard layout.  

Yes; this is the conclusion I've come to, worded in a different way.  I've noticed that the concepts of memcpy being able to memberwise copy members of a class and being able to use offsetof are pretty much equivalent, when combined with other rules of C++.  It leads to a breakdown of the current "hard" definition of trivially-copyable where the compiler supposedly could do crazy class memory layouts when not also standard-layout.  It leads to inconsistencies, and I think that the only way to resolve them is to split along the "simple memory layout" axis and the "special handling required for copying/moving" axis, rather than the current way the Standard handles things.  The "soft" definition of trivially-copyable then refers to the logical AND of whether memcpy is safe at the implementation level and of whether the class has a trivial copy constructor.

The "simple memory layout" axis is equivalent to "memcpy is well-defined and does a memberwise copy" and "offsetof is well-defined", in my view.  Whether having virtual functions places classes outside the "simple memory layout" axis or inside the "special handling required for copying/moving" category would be implementation-specific, because in many implementations, memcpying all the vtable pointers would work just fine (assuming you're copying/moving the most-derived type).

I don't see why we need the path. If std::is_empty_aggregate<Base>::value is
true, you should never attempt to copy it, whether it be sub-object of a
larger object or full object. If it's a full object, it contains one byte of
unspecified value that mustn't be read from or written to.


I suppose so, but it would seem to be a little odd to have e.g. an array of standard-layout objects that you can't memcpy.  Perhaps rather than the two extremes of never copying empties and trying to be extremely exact like my path solution, what about the intermediate solution of saying that memcpying when std::is_empty_aggregate<T>::value is true is only permissible when T is the most-derived type of the object?
 
Why do we need a definition for memcpy-compatible-layout to be different from
that of standard layout in the first place? Are we trying to capture the case
of non-empty deriving from non-empty? Those aren't standard layout but under
most ABIs they could be memcpy'ed whether they are sub-objects or full
objects. In fact, they are trivially copyable.

If that's the use-case you're aiming for, I have to ask first why standard
layout was restricted to only one non-empty class in the hierarchy. There must
have been a reason why C++11 gave it to us like that and that might be a
limitation on the ability to memcpy a full object.


I separated the two because memcpy-compatible-layout permits mixing public/protected/private non-static data members and base classes, which on some rare existing implementations supposedly causes coalescing along access-specifier categories.  This wouldn't be compatible with C and other native languages, which was always the point of standard-layout.  Otherwise, you're right; I have no reason to care about the distinction.  If it weren't for the fact that standard-layout classes are allowed to have protected or private non-static data members, this would be a moot point, because then we could just say if you want to guarantee C compatibility, your first members have to all be public, rather than the current requirement of merely having to all be the same access-specifier, and non-public members are inaccessible to C (and if you have any, don't try to use arrays of that class or sizeof).

If I were able to redesign it all, I'd define the memory layout rules as being the current architecture's C structure and union format.  Convert C++ classes into C structures by placing at the beginning of the C structure a data member for each of the base classes that was not elided to zero size, in listed/constructor order, then each non-static data member, in listed/constructor order.  References, data member pointers and function member pointers would be represented as implementation-specific structure types.  The padding rules would then be identical to C and handled as such.  Classes with virtual functions would work by allowing the implementation to insert extra data members.  Virtual inheritance... well, all bets are off at that point, have fun.  So long as base classes that don't have virtual base classes of their own still had to be C structures, this would be fine.

Though perhaps this would have been a bit more constraining, it would have simplified things, and made it very obvious how C++ classes interacted with C.

BTW, I think we have now four traits to specify:

 - s_empty_aggregate        (because sizeof(X) can't be trusted)
 - is_contiguous                (something like your definition above, with better name)
                those two are mutually exclusive
 - is_memcpy_relocatable
 - is_memcpy_clonable

The latter two are true for all trivially copyable types and false for others,
but may be overridden by the user to indicate that their type is relocatable
or clonable.


I'm not sure of the names, but these are the relevant traits that I can see:

1. Class with no non-static data; in most cases, it would get elided to zero size if it were a base class.  Same as your is_empty_aggregate, but the Standard's existing is_empty is also equivalent as far as I can tell; it is also false for classes with virtual bases or virtual functions.
2. Class for which on the current implementation memcpy is well-defined as being a memberwise copy of all scalars, even if doing so may be a bad idea for other reasons (i.e. this trait is true for std::string).  offsetof(C, m) would also be a well-defined constexpr for class C and member m (recursively) of C.  I suppose that this trait's definition matches your is_contiguous.  It also looks like it might equal !is_polymorphic<T>::value && !some new trait that honestly should've already existed like has_virtual_base<T>::value.  This is one that compilers could set to true for more than the required types, though.
3. Class whose layout is compatible with C; this is currently is_standard_layoutis_standard_layout implies whatever trait #2 is.  Whether #2 and #3 are identical, other than certain formations being incompatible in C on their own, is the topic of what I wrote earlier in this email. =^-^=
4. Class that can be safely relocated with memcpy from both an implementation perspective and from the class's "permission".  This is your is_memcpy_relocatable, but to me it seems like we could create a new name for this by analogy with existing STL: is_trivially_movable.  It would just equal is_trivially_move_constructible<T>::value && is_trivially_move_assignable<T>::value && is_trivially_destructible<T>::value; the is_trivially_move_... ones would be adjusted to also require is_contiguous<T>::value to be true, by changing the definition of is_trivially_constructible.
5. Class that can be safely relocated with memcpy from both an implementation perspective and from the class's "permission".  Like #4, this is your is_memcpy_clonable.  STL already has is_trivially_copyable; we could use this and merely adjust the definition of it to be is_trivially_movable<T>::value && is_trivially_copy_constructible<T>::value && is_trivially_copy_assignable<T>::value.  Then trivially-copyable disappears from the Standard as a keyword/key phrase.

If is_contiguous<T>::value is true, it'd be nice for a class to be able to override #4 and #5 somehow, especially to force them true when it would otherwise be false, but forcing false I suppose has its uses as well.

Melissa

Thiago Macieira

unread,
Sep 25, 2014, 4:12:42 AM9/25/14
to std-dis...@isocpp.org
On Thursday 25 September 2014 00:13:15 Myriachan wrote:
> On Wednesday, September 24, 2014 4:47:28 PM UTC-7, Thiago Macieira wrote:
> > I think you're putting the cart ahead of the horse.
> >
> > We already have the class of types that are contiguous: standard layout.
> > We
> > just need a way to restrict the group of all standard layout types to all
> > those that can be memcpy-relocated and those that can be memcpy-cloned.
>
> Oh, are you saying to extend the definition of *standard-layout* to include
> classes that would currently be *trivially-copyable* but not
> *standard-layout*? For example, classes with non-static members in
> multiple levels of the hierarchy, or mixed member or base class
> *access-specifiers*. If it's called *standard-layout* instead of
> *memcpy-compatible*, I would be fine with that; I just care about the
> concepts.

No. See below: I want to know why those classes aren't standard layout in the
first place. Maybe there's a reason and that might make those classes not
properly memcpy'able.

> By the way, from your quoting of my message, I see that some of my text has
> asterisks. When I write my messages, these words were entered as italics,
> so I suppose this formatting is lost for some recipients of this list. I

There is no formatting in plain text. I do not read HTML email in mailing
lists and my client will use the text portion of the email in quoting for the
reply.

> The "simple memory layout" axis is equivalent to "memcpy is well-defined
> and does a memberwise copy" and "offsetof is well-defined", in my view.
> Whether having virtual functions places classes outside the "simple memory
> layout" axis or inside the "special handling required for copying/moving"
> category would be implementation-specific, because in many implementations,
> memcpying all the vtable pointers would work just fine (assuming you're
> copying/moving the most-derived type).

The standard can't talk about what some implementations may do and others
won't. Outside of compelling reason, I'd say we err on the side of caution and
assume the implementation is the worst possible. This will exclude a few cases
of classes, but in the majority of them I won't expect a problem. Let's just
leave virtuals alone, they are not copyable in any ABI, period.

>
> I don't see why we need the path. If std::is_empty_aggregate<Base>::value
>
> > is
> > true, you should never attempt to copy it, whether it be sub-object of a
> > larger object or full object. If it's a full object, it contains one byte
> > of
> > unspecified value that mustn't be read from or written to.
>
> I suppose so, but it would seem to be a little odd to have e.g. an array of
> *standard-layout* objects that you can't memcpy. Perhaps rather than the
> two extremes of never copying empties and trying to be extremely exact like
> my path solution, what about the intermediate solution of saying that
> memcpying when std::is_empty_aggregate<T>::value is true is only
> permissible when T is the most-derived type of the object?

You can copy, so long as you multiply the length of the array by the object's
true size: zero. Then you'll always copy the number of elements times zero and
that is a well-defined and correct operation.

I don't see why we want to make it more difficult by deciding whether it's a
true object or a sub-object. How about true-object-but-in-construction? How
about virtual sub-object?

> Though perhaps this would have been a bit more constraining, it would have
> simplified things, and made it very obvious how C++ classes interacted with
> C.

The only classes that can interact with C are the POD classes. We don't need
to define anything further if all we wanted is C interaction.

Trivially copyable and standard layout were split off POD's definition for some
reason.

> BTW, I think we have now four traits to specify:
> > - s_empty_aggregate (because sizeof(X) can't be trusted)
> > - is_contiguous (something like your definition above,
> >
> > with better name)
> >
> > those two are mutually exclusive
> >
> > - is_memcpy_relocatable
> > - is_memcpy_clonable
> >
> > The latter two are true for all trivially copyable types and false for
> > others,
> > but may be overridden by the user to indicate that their type is
> > relocatable
> > or clonable.
>
> I'm not sure of the names, but these are the relevant traits that I can see:
>
> 1. Class with no non-static data; in most cases, it would get elided to
> zero size if it were a base class. Same as your is_empty_aggregate, but
> the Standard's existing is_empty is also equivalent as far as I can tell;
> it is also false for classes with virtual bases or virtual functions.

I missed std::is_empty's existence. If it's already there, then problem
solved.

> 2. Class for which on the current implementation memcpy is well-defined as
> being a memberwise copy of all scalars, even if doing so may be a bad idea
> for other reasons (i.e. this trait is true for std::string). offsetof(C, m)
> would also be a well-defined constexpr for class C and member m
> (recursively) of C. I suppose that this trait's definition matches your
> is_contiguous. It also looks like it might equal !is_polymorphic<T>::value
> && !some new trait that honestly should've already existed like
> has_virtual_base<T>::value. This is one that compilers could set to true
> for more than the required types, though.

I suppose so, but we need to find some definition that means what we want and
possibly doesn't make reference to memcpy.

> 3. Class whose layout is compatible with C; this is currently
> is_standard_layout. is_standard_layout implies whatever trait #2 is.
> Whether #2 and #3 are identical, other than certain formations being
> incompatible in C on their own, is the topic of what I wrote earlier in
> this email. =^-^=

Right.

> 4. Class that can be safely relocated with memcpy from both an
> implementation perspective and from the class's "permission". This is your
> is_memcpy_relocatable, but to me it seems like we could create a new name
> for this by analogy with existing STL: is_trivially_movable. It would just
> equal is_trivially_move_constructible<T>::value &&
> is_trivially_move_assignable<T>::value &&
> is_trivially_destructible<T>::value; the is_trivially_move_... ones would
> be adjusted to also require is_contiguous<T>::value to be true, by changing
> the definition of is_trivially_constructible.

I don't want to change the core language concept of "trivially movable". I
want to leave std::is_trivially_movable and std::is_triivally_copyable alone.
You're not allowed to specialise it, since it's supposed to reflect the
language concept, not the class author's possibly erroneous ideas.

I want a separate trait.

> 5. Class that can be safely relocated with memcpy from both an
> implementation perspective and from the class's "permission". Like #4,
> this is your is_memcpy_clonable. STL already has is_trivially_copyable; we
> could use this and merely adjust the definition of it to be
> is_trivially_movable<T>::value && is_trivially_copy_constructible<T>::value
> &&
> is_trivially_copy_assignable<T>::value. Then *trivially-copyable*
> disappears from the Standard as a keyword/key phrase.

Ditto.

> If is_contiguous<T>::value is true, it'd be nice for a class to be able to
> override #4 and #5 somehow, especially to force them true when it would
> otherwise be false, but forcing false I suppose has its uses as well.

I don't see why someone would force to false, since false is the default. It
would be true only for classes that are trivially copyable already, which
means memberwise copy and memcpy are the same operation.
It is loading more messages.
0 new messages