[llvm-bugs] [Bug 49002] New: Incorrect computation of trivially copyable for class with user-declared move assignment operator, defined as deleted

via llvm-bugs llvm-bugs at lists.llvm.org
Tue Feb 2 08:35:55 PST 2021


https://bugs.llvm.org/show_bug.cgi?id=49002

            Bug ID: 49002
           Summary: Incorrect computation of trivially copyable for class
                    with user-declared move assignment operator, defined
                    as deleted
           Product: clang
           Version: trunk
          Hardware: PC
                OS: Windows NT
            Status: NEW
          Severity: enhancement
          Priority: P
         Component: C++
          Assignee: unassignedclangbugs at nondot.org
          Reporter: andrew.rogers at wdc.com
                CC: blitzrakete at gmail.com, dgregor at apple.com,
                    erik.pilkington at gmail.com, llvm-bugs at lists.llvm.org,
                    richard-llvm at metafoo.co.uk

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 clang trunk build on Godbolt (clang version
13.0.0 (https://github.com/llvm/llvm-project.git
54842fa0bba0c6cf69b7eb94f4b10d8da8aa5170)) fails with:

<source>:10:1: error: static_assert failed due to requirement
'!std::is_trivially_copyable<Bar>::value' "Bar is trivially copyable"
static_assert(!std::is_trivially_copyable<Bar>::value, "Bar is trivially
copyable");
^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
Compiler returned: 1

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

Thanks, Andrew R

-- 
You are receiving this mail because:
You are on the CC list for the bug.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-bugs/attachments/20210202/5d7ee7ef/attachment.html>


More information about the llvm-bugs mailing list