Bug 98936 - [DR1734] Incorrect computation of trivially copyable for class with user-declared move assignment operator, defined as deleted
Summary: [DR1734] Incorrect computation of trivially copyable for class with user-decl...
Status: UNCONFIRMED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 11.0
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: accepts-invalid, rejects-valid, wrong-code
Depends on:
Blocks:
 
Reported: 2021-02-02 16:18 UTC by Andrew Rogers
Modified: 2023-11-15 19:00 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Andrew Rogers 2021-02-02 16:18:49 UTC
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
Comment 1 Andrew Pinski 2021-08-17 09:33:41 UTC
Interesting only MSVC is able to pass the static_assert here.
Comment 2 Roland Schulz 2021-11-01 21:23:56 UTC
This might be a duplicate of #96288 .