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