31

Is this class:

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

trivially copyable? (At least clang seems to think so (live))

In particular, would

A a,b;
std::memcpy(&a, &b, sizeof(A));

invoke undefined behavior?

Context: This answer [deleted because proven wrong] plus its comment tree.

Community
  • 1
  • 1
Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
  • Why do you think memcpy would behave differently if the copy constructor wasn't deleted? – iheanyi Apr 20 '15 at 22:28
  • 5
    @iheanyi: Because there are rules about what you can and cannot do with the storage that backs an object. – Lightness Races in Orbit Apr 20 '15 at 22:31
  • @LightningRacisinObrit No there is not. I can do whatever I want with it. – iheanyi Apr 20 '15 at 22:33
  • 5
    @iheanyi Your arguments are devolving into nonsense. You can `memcpy` anything to your heart's content. But once you do that, whether you can use the destination storage as if it were an object of the original type is governed by the rules T.C.'s answer lists below. – Praetorian Apr 20 '15 at 22:37
  • 16
    @iheanyi "You can" as in "you are allowed by the language specification". It's just like how someone might say you can't run a red light. Do you step on the gas, laugh, and say "oh yes I can, see?!!"? –  Apr 20 '15 at 22:37
  • 5
    @iheanyi: When the standard does not explicitly define the behavior of something (like `memcpy`ing non trivially-copyable objects), the compiler is free to do whatever it wants, including optimizing away the call or crashing the program, or doing something which *looks like* what you would expect, but crashes the program later (or not). This can depend on compiler flags, optimization settings, etc. So this *is* important to understand what is and is not allowed as a programmer. – Alexandre C. Apr 20 '15 at 22:50
  • @AlexandreC. I get part of your comment. However, unless I'm misunderstanding something, there is nothing undefined about the behavior of memcpy when all parameters are valid. Edit - The question here should be about the behavior of the copy. – iheanyi Apr 20 '15 at 22:53
  • 3
    @iheanyi: "valid" in this case means "trivially copyable" (this is the whole point of the question). Also the compiler is not required to issue a diagnostic. All these rules are meant to give a framework for various optimizations to occur while still being able to rely on something. The way `memcpy` is implemented or optimized within your program (the compiler can reorder a lot of things and treat memcpy specially for instance) must respect the standard, but that's all. Also your program can work in one version of a compiler and break later as the compiler evolves. – Alexandre C. Apr 20 '15 at 22:55
  • 3
    @iheanyi: you can read this : http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html for a flavor of what "undefined behavior" means. – Alexandre C. Apr 20 '15 at 22:57
  • 1
    @AlexandreC. Thanks, the last example there helps with this particular question. Downvote removed. – iheanyi Apr 20 '15 at 23:00

1 Answers1

30

Update: The proposed resolution of CWG 1734, currently in "ready" status, would modify [class]/p6 to read:

A trivially copyable class is a class:

  • where each copy constructor, move constructor, copy assignment operator, and move assignment operator (12.8 [class.copy], 13.5.3 [over.ass]) is either deleted or trivial,
  • that has at least one non-deleted copy constructor, move constructor, copy assignment operator, or move assignment operator, and
  • that has a trivial, non-deleted destructor (12.4 [class.dtor]).

This renders classes like

struct B {
    B() = default;
    B(const B&) = delete;
    B& operator=(const B&) = delete;
};

no longer trivially copyable. (Classes of this sort include synchronization primitives like std::atomic<T> and std::mutex.)

However, the A in the OP has a implicitly declared, non-deleted copy assignment operator that is trivial, so it remains trivially copyable.

The original answer for the pre-CWG1734 situation is preserved below for reference.


Yes, somewhat counterintuitively, it is trivially copyable. [class]/p6:

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).

[class.copy]/p12:

A copy/move constructor for class X is trivial if it is not user-provided, its parameter-type-list is equivalent to the parameter-type-list of an implicit declaration, and if

  • class X has no virtual functions (10.3) and no virtual base classes (10.1), and
  • class X has no non-static data members of volatile-qualified type, and
  • the constructor selected to copy/move each direct base class subobject is trivial, and
  • for each non-static data member of X that is of class type (or array thereof), the constructor selected to copy/move that member is trivial;

Similarly ([class.copy]/p25):

A copy/move assignment operator for class X is trivial if it is not user-provided, its parameter-type-list is equivalent to the parameter-type-list of an implicit declaration, and if

  • class X has no virtual functions (10.3) and no virtual base classes (10.1), and
  • class X has no non-static data members of volatile-qualified type, and
  • the assignment operator selected to copy/move each direct base class subobject is trivial, and
  • for each non-static data member of X that is of class type (or array thereof), the assignment operator selected to copy/move that member is trivial;

[class.dtor]/p5:

A destructor is trivial if it is not user-provided and if:

  • the destructor is not virtual,
  • all of the direct base classes of its class have trivial destructors, and
  • for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.

[dcl.fct.def.default]/p5:

A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration.

Indeed, this has been a source of problems for the committee itself, because under the current definition atomic<T> (along with mutexes and condition variables) would be trivially copyable. (And obviously, allowing someone to memcpy over an atomic or a mutex without invoking UB would be ... let's just say seriously problematic.) See also N4460.

T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Heh, you already covered more than I did by the time I answered. Nice find, that might mean that even if it's currently trivially copyable, it might not remain trivially copyable. –  Apr 20 '15 at 22:34
  • @hvd The current proposed resolution to that LWG issue is basically a magic incantation ("no matter what the core language says, this is not trivially copyable!!!"). There's a paper in the pre-Lenexa mailing discussing various possible ways to fix this. – T.C. Apr 20 '15 at 22:37
  • Seems like the correct fix would be "HAS a trivial copy-constructor or a trivial move-constructor that is not deleted AND HAS a trivial copy-assignment or move-assignment operator that is not deleted". If there is a valid copy which is also trivial, then trivial copy is valid. Makes no difference that there might also be a fancier copy. – Ben Voigt Apr 21 '15 at 00:19
  • @BenVoigt See the discussion in N4460; apparently making deleted special members trivial was an intentional decision. – T.C. Apr 21 '15 at 01:36
  • 3
    @T.C.: A horribly broken decision. What does it mean in the triviality test for a deleted constructor that "the constructor selected to copy/move each direct base class subobject is trivial"? The selection doesn't happen, there is no *constructor selected to copy/move each subobject*. Should it read "the constructor that would have been selected, had the derived constructor not been deleted"? But then what if selection would have been impossible, because the base or member constructor was deleted? Broken, broken, broken. – Ben Voigt Apr 21 '15 at 01:54