[Bug c++/98936] New: Incorrect computation of trivially copyable for class with user-declared move assignment operator, defined as deleted

adr26__gcc at nunsway dot co.uk gcc-bugzilla@gcc.gnu.org
Tue Feb 2 16:18:49 GMT 2021


https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98936

            Bug ID: 98936
           Summary: Incorrect computation of trivially copyable for class
                    with user-declared move assignment operator, defined
                    as deleted
           Product: gcc
           Version: 11.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: adr26__gcc at nunsway dot co.uk
  Target Milestone: ---

Consider the following code:

   #include <type_traits>

   class Bar {
   public:
      int A;
      // User-declared move assignment operator, defined as deleted
      Bar& operator=(Bar&&) = delete;
   };

   static_assert(!std::is_trivially_copyable<Bar>::value, "Bar is trivially
copyable");

>From C++11 to C++17, as per CWG 1734
[http://www.open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1734], the
definition of a trivially copyable class was as follows (all references taken
from C++17) - in §12 "Classes" [class], §12/6 defines a trivially copyable
class:

   “A trivially copyable class is a class:

    (6.1) — where each copy constructor, move constructor, copy assignment
operator, and move assignment operator (15.8, 16.5.3) is either deleted or
trivial,
    (6.2) — that has at least one non-deleted copy constructor, move
constructor, copy assignment operator, or move assignment operator, and
    (6.3) — that has a trivial, non-deleted destructor (15.4).”

1. For §12/6.1 - we can look at copy constructors, move constructors, copy
assignment operators, and move assignment operators in turn.

   a. For copy constructors: Bar has no user-declared copy constructor. In
§15.8.1 "Copy/move constructors" [class.copy.ctor], §15.8.1/6 states that a
non-explicit copy constructor will be implicitly declared in the absence of a
user-declared copy constructor:

         “If the class definition does not explicitly declare a copy
constructor, a non-explicit one is declared implicitly. If the class definition
declares a move constructor or move assignment operator, the implicitly
declared copy constructor is defined as deleted; otherwise, it is defined as
defaulted (11.4). The latter case is deprecated if the class has a
user-declared copy assignment operator or a user-declared destructor.”

      Since Bar has a user-declared move assignment operator, this
implicitly-declared copy constructor is defined as deleted and §12/6.1 is
therefore satisfied with respect to copy constructors.

   b. For move constructors: the class Bar has no user-declared move
constructors, and so therefore may only have an implicitly declared move
constructor. §15.8.1/8 specifies the exact and sole conditions under which a
move constructor may be implicitly declared:

         “If the definition of a class X does not explicitly declare a move
constructor, a non-explicit one will be implicitly declared as defaulted if and
only if

          (8.1) — X does not have a user-declared copy constructor,
          (8.2) — X does not have a user-declared copy assignment operator,
          (8.3) — X does not have a user-declared move assignment operator, and
          (8.4) — X does not have a user-declared destructor.”

      We may note that in particular that while Bar does not have an explicitly
declared move constructor, Bar does have a user-declared move assignment
operator so condition §15.8.1/8.3 is not met in any case. As such, Bar will not
have neither a user- nor an implicitly-declared move constructor and therefore
§12/6.1 is trivially satisfied with respect to move constructors (since there
are none).

   c. For copy assignment operators: the class Bar has no user-declared copy
assignment operators, and so therefore may only have an implicitly declared
copy assignment operators. §15.8.2 "Copy/move assignment operator"
[class.copy.assign] describes these and §15.8.2/2 specifies how a copy
assignment operator will always be implicitly declared if the class has no
user-declared copy assignment operators:

         “If the class definition does not explicitly declare a copy assignment
operator, one is declared implicitly. If the class definition declares a move
constructor or move assignment operator, the implicitly declared copy
assignment operator is defined as deleted; otherwise, it is defined as
defaulted (11.4). The latter case is deprecated if the class has a
user-declared copy constructor or a user-declared destructor.”

      Since Bar has a user-declared move assignment operator, this
implicitly-declared copy assignment operator is defined as deleted and §12/6.1
is therefore satisfied with respect to copy assignment operators.

   d. For move assignment operators: Bar has one user-declared move assignment
operator, defined as deleted - which therefore satisfies §12/6.1.

   Therefore, for each of the copy constructors, move constructors, copy
assignment operators, and move assignment operators of Bar, §12/6.1 is
satisfied.

2. For §12/6.2 - by virtue of 1. above, Bar has:

   1.a. - one implicitly-declared copy constructor, defined as deleted. 
   1.b. - no move constructors. 
   1.c. - one implicitly-declared copy assignment operator, defined as deleted. 
   1.d. - one user-declared move assignment operator, defined as deleted.

   Therefore, Bar does not have one (or more) non-deleted copy constructor,
move constructor, copy assignment operator, or move assignment operator (since
all those that exist are deleted) and it cannot satisfy §12/6.2.

Since requirement §12/6.2 for a trivially copyable class is not satisfied by
Bar, we should have (std::is_trivially_copyable<Bar>::value == false).

In C++20 (and taking all future references from C++20), the definitions have
been moved around, but have the same effect. The definition of a trivially
copyable class is now in §11.2 "Properties of classes" [class.prop], where
§11.2/1 states that:

   “A trivially copyable class is a class:

    (1.1) — that has at least one eligible copy constructor, move constructor,
copy assignment operator, or move assignment operator (11.4.4, 11.4.5.3,
11.4.6),
    (1.2) — where each eligible copy constructor, move constructor, copy
assignment operator, and move assignment operator is trivial, and
    (1.3) — that has a trivial, non-deleted destructor (11.4.7).”

So that C++20 now requires that there is at least one of the enumerated special
member functions which is eligible, rather than just non-deleted. However, as
§11.4.3 "Special member functions" [special] explains, in §11.4.3/6, eligible
special member functions are a subset of non-deleted functions:

   “An eligible special member function is a special member function for which:

    (6.1) — the function is not deleted,
    (6.2) — the associated constraints (13.5), if any, are satisfied, and
    (6.3) — no special member function of the same kind is more constrained
(13.5.4).”

In a similar manner to that shown above for C++17, you can see that in C++20
Bar has no eligible copy constructor, move constructor, copy assignment
operator, or move assignment operator. Each of these special member functions
is either not implicitly- or explicitly-declared (move constructor), or the
function is defined as deleted and so is not eligible (all others). Therefore
the requirement in §11.4.3/6.1 that there must be at least one eligible copy
constructor, move constructor, copy assignment operator, or move assignment
operator is not satisfied and Bar continues to be not trivially copyable in
C++20 onwards.

Therefore, the above code should compile cleanly for any version of C++ from
C++11 onwards, but the current GCC trunk build on Godbolt (11.0.0 20210131
(experimental)) fails with:

<source>:10:15: error: static assertion failed: Bar is trivially copyable
   10 | static_assert(!std::is_trivially_copyable<Bar>::value, "Bar is
trivially copyable");
      |               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Compiler returned: 1

https://gcc.godbolt.org/z/nYKd6n

Thanks, Andrew R


More information about the Gcc-bugs mailing list