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