Re: [Boost-bugs] [Boost C++ Libraries] #11835: boost::has_trivial_copy is incorrect on Clang

Subject: Re: [Boost-bugs] [Boost C++ Libraries] #11835: boost::has_trivial_copy is incorrect on Clang
From: Boost C++ Libraries (noreply_at_[hidden])
Date: 2015-12-13 15:36:18


#11835: boost::has_trivial_copy is incorrect on Clang
-------------------------+-------------------------------------------------
  Reporter: | Owner: johnmaddock
  joseph.thomson@… | Status: closed
      Type: Bugs | Component: type_traits
 Milestone: To Be | Severity: Problem
  Determined | Keywords: has_trivial_copy clang memcpy
   Version: Boost | is_trivially_copyable
  1.59.0 |
Resolution: wontfix |
-------------------------+-------------------------------------------------

Comment (by joseph.thomson@…):

> I just checked them all with gcc 4.6.4, 4.7.4, 4.8.4, 4.9.2, 5.1.0, plus
 clang and Intel and they all do the correct thing - which is to say they
 all regard deleted operators as non-trivial. This is in 1.60 beta BTW.
 Also verified our tests check this. Note that all these traits have been
 completely rewritten for the next release.

 That's good!

> One final point: has_trivial_copy and friends were developed for C++03
 (and later included in TR1 standard). There's a lot of code out there
 which relies on has_trivial_copy implies safe-to-memcpy instead of copy
 construct. That code should absolutely not be broken just because C++11
 has introduced new features: ie deleted functions.

 I agree. It's a shame that Boost.TypeTraits' definition of "trivial"
 special functions doesn't match the language specification, but not
 breaking existing code is more important technically correct naming. Have
 you considered providing aliases (e.g. `boost::is_copy_constructible`)
 that match the standard library?

> We have never provided is_trivially_copyable, if we did it would likely
 be composed from these traits. Plus I believe you have this backwards -
 is_trivially_copy_constructible is the std trait - and there is a
 precondition on that trait that the type be copy-constructible, from
 C++14:
>
> "For a referenceable type T, the same result as
 is_trivially_constructible<T, const T&>::value, otherwise false."
>
> and then is_trivially_constructible 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)."
> Which would rule out types with deleted copy-constructors from being
 trivially-copyable.

 Classes with deleted copy constructors ''can'' be trivially copyable (as I
 will demonstrate). However, the issue I was trying to raise here was that
 trivially copy constructible types are ''not necessarily'' trivially
 copyable, so it is potentially undefined behaviour to `memcpy` them.

 I think you may be confusing three different concepts:

 '''triviality of special functions'''

 [class.copy]

> 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;
> otherwise the copy/move constructor is non-trivial.

> 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;
> otherwise the copy/move assignment operator is non-trivial.

> 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.
> Otherwise, the destructor is non-trivial.

 [dcl.fct.def.default]

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

 This all means that deleted special functions ''can'' be trivial. It
 doesn't matter how `boost::has_trivial_copy` behaves (you can make it do
 whatever you like); this is the standard's definition of what constitutes
 a trivial special function, and it is essential to when interpreting the
 standard's definition of "trivially copyable".

 '''trivial copyability of classes'''

 [class]

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

 Since deleted special function ''can'' be trivial, according to the
 standard, this means that this function ''is'' trivially copyable, however
 counter-intuitive it may be:

 {{{
 struct X
 {
     X(X const&) = delete;
     X& operator=(X const&) = delete;
     X(X&&) = delete;
     X& operator=(X&&) = delete;
 };
 }}}

 However, a proposal has been submitted to change the definition of
 "trivially copyable", because under the current definition, classes like
 `std::atomic` and `std::mutex` are trivially copyable.

 http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1734

 This proposal suggests that "trivially copyable" classes have to have at
 least one non-deleted copy/move special functions (and that the destructor
 be non-deleted):

> A trivially copyable class is a class:
> * where each copy constructor, move constructor, copy assignment
 operator, and move assignment operator (12.8, 13.5.3) 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).

 This would make the aforementioned class no longer trivially copyable.
 However, a trivially copyable class does ''not'' necessarily require a
 non-deleted copy constructor; any non-deleted special function will fulfil
 the requirements. Thus, this class would still be trivially copyable:

 {{{
 struct X
 {
     const int val;
 };
 }}}

 I took this example from a C++ standards committee document:

 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4460.html

 So in their words:

> So, why don't we just say that a deleted special member function is non-
 trivial? Alas, that would mean the following type becomes non-trivial:
>
> {{{
> struct X
> {
> const int val;
> };
> }}}
>
> X has a deleted copy assignment operator. That doesn't mean that X is
 not trivially copyable. And X has been trivial since the dawn of time, so
 we must be careful not to break such expectations.

 '''trivial constructibility/assignability of classes'''

 You already quoted the standard, so I don't have to.

 For a class to be trivially copy constructible, it must be '''copy
 constructible''', and the copy constructor must be '''trivial'''. Since
 you cannot copy construct an object using a ''deleted'' copy constructor,
 I agree that a class with a deleted copy constructor is ''not'' trivially
 copy constructible. However, copy constructibility is ''not'' a
 requirement of trivial copyability; the presence of a trivial copy
 constructor ''is'' (even a deleted one). Even under the proposed
 amendment, as long as there is at least one other non-deleted copy/move
 function, a class does ''not'' have to be copy constructible to be
 trivially copyable.

 '''In conclusion...'''

 In other words, both trivially copyable and trivially copy constructible
 classes ''must'' have trivial copy constructors (deleted copy constructors
 ''may'' be trivial), but only trivially copy constructible classes
 ''must'' be copy constructible.

 But the most important thing is this:

 [intro.object]

> An object of trivially copyable or standard-layout type (3.9) shall
 occupy contiguous bytes of storage.

 [basic.types]

> 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.
>
> {{{
> 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
> }}}

 Thus, using `memcpy` to copy a non-trivially copyable class would be
 undefined behaviour. Since trivially copy constructible classes are not
 necessarily trivially copyable, relying on `boost::has_trivial_copy` to
 tell you whether it's safe to use `memcpy` could lead to undefined
 behaviour. This is why I say that your documentation needs to be changed
 (and potentially, `boost::is_trivially_copyable` added).

 Note that you could implement a `boost::is_trivially_copyable` that
 requires all copy/move special functions to be non-deleted. While this
 would be overly-conservative (e.g. the aforementioned `X` class would
 register as non-trivially copyable), it would at least be sufficient to
 check for `memcpy`-ability.

-- 
Ticket URL: <https://svn.boost.org/trac/boost/ticket/11835#comment:7>
Boost C++ Libraries <http://www.boost.org/>
Boost provides free peer-reviewed portable C++ source libraries.

This archive was generated by hypermail 2.1.7 : 2017-02-16 18:50:19 UTC