llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
28.03k stars 11.58k forks source link

std::is_trivially_copyable gives wrong value if constraint on copy assignment operator is not satisfied with clang16/trunk #63352

Open wanghan02 opened 1 year ago

wanghan02 commented 1 year ago

Code as below or on Godbolt. Clang 16/trunk believes S<int> is not a trivially copyable class. Clang 15, GCC trunk and MSVC believe otherwise.

#include <type_traits>

template<typename T>
struct S {
    T m_t;
    S(S const&) = default;
    S(S&&) = default;
    S& operator=(S const&) requires (!std::is_integral<T>::value) = default;
    ~S() = default;
};

// next five assertions pass for all compilers
static_assert(std::is_trivially_destructible<S<int>>::value);
static_assert(std::is_trivially_copy_constructible<S<int>>::value);
static_assert(std::is_trivially_move_constructible<S<int>>::value);
static_assert(!std::is_copy_assignable<S<int>>::value);
static_assert(!std::is_move_assignable<S<int>>::value);

// compiles with gcc trunk, MSVC and clang 15, fails with clang 16/trunk
static_assert(std::is_trivially_copyable<S<int>>::value);

According to the standard:

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 ([special], [class.copy.ctor], [class.copy.assign]), (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 ([class.dtor]).

An eligible special member function is a special member function for which: (6.1) the function is not deleted, (6.2) the associated constraints ([temp.constr]), if any, are satisfied, and (6.3) no special member function of the same kind is more constrained ([temp.constr.order]).

S<int> has trivial copy/move constructor and trivial destructor. Its copy assignment operator is not eligible because its constraint is not satisfied. Its move assignment operator is not eligible because it's not declared. Is this a Clang bug?

llvmbot commented 1 year ago

@llvm/issue-subscribers-clang-frontend

shafik commented 1 year ago

CC @royjacobson I think this is related to https://reviews.llvm.org/D128619 but I am not sure what the correct outcome here is

royjacobson commented 1 year ago

Looks as it should be trivially copyable, I'll take a look. Thanks!

royjacobson commented 1 year ago

So we have this method in CXXRecordDecl

  bool hasNonTrivialCopyAssignment() const {
    return data().DeclaredNonTrivialSpecialMembers & SMF_CopyAssignment ||
           !hasTrivialCopyAssignment();
  }

and it returns a wrong result in this case: no non trivial copy assignment is present but no trivial one exists either. Need to check what the actual logic should be.

llvmbot commented 1 month ago

@llvm/issue-subscribers-c-20

Author: Han (wanghan02)

Code as below or on [Godbolt](https://godbolt.org/). Clang 16/trunk believes `S<int>` is not a trivially copyable class. Clang 15, GCC trunk and MSVC believe otherwise. ```cpp #include <type_traits> template<typename T> struct S { T m_t; S(S const&) = default; S(S&&) = default; S& operator=(S const&) requires (!std::is_integral<T>::value) = default; ~S() = default; }; // next five assertions pass for all compilers static_assert(std::is_trivially_destructible<S<int>>::value); static_assert(std::is_trivially_copy_constructible<S<int>>::value); static_assert(std::is_trivially_move_constructible<S<int>>::value); static_assert(!std::is_copy_assignable<S<int>>::value); static_assert(!std::is_move_assignable<S<int>>::value); // compiles with gcc trunk, MSVC and clang 15, fails with clang 16/trunk static_assert(std::is_trivially_copyable<S<int>>::value); ``` According to the standard: > A [trivially copyable class](https://timsong-cpp.github.io/cppwp/n4868/class#def:class,trivially_copyable) is a class: > [(1.1)](https://timsong-cpp.github.io/cppwp/n4868/class#prop-1.1) that has at least one eligible copy constructor, move constructor, copy assignment operator, or move assignment operator ([[special]](https://timsong-cpp.github.io/cppwp/n4868/class#special), [[class.copy.ctor]](https://timsong-cpp.github.io/cppwp/n4868/class#copy.ctor), [[class.copy.assign]](https://timsong-cpp.github.io/cppwp/n4868/class#copy.assign)), > [(1.2)](https://timsong-cpp.github.io/cppwp/n4868/class#prop-1.2) where each eligible copy constructor, move constructor, copy assignment operator, and move assignment operator is trivial, and > [(1.3)](https://timsong-cpp.github.io/cppwp/n4868/class#prop-1.3) that has a trivial, non-deleted destructor ([[class.dtor]](https://timsong-cpp.github.io/cppwp/n4868/class#dtor))[.](https://timsong-cpp.github.io/cppwp/n4868/class#prop-1.sentence-1) > An [eligible special member function](https://timsong-cpp.github.io/cppwp/n4868/class#def:special_member_function,eligible) is a special member function for which: > [(6.1)](https://timsong-cpp.github.io/cppwp/n4868/class#special-6.1) the function is not deleted, > [(6.2)](https://timsong-cpp.github.io/cppwp/n4868/class#special-6.2) the associated constraints ([[temp.constr]](https://timsong-cpp.github.io/cppwp/n4868/temp.constr)), if any, are satisfied, and > [(6.3)](https://timsong-cpp.github.io/cppwp/n4868/class#special-6.3) no special member function of the same kind is more constrained ([[temp.constr.order]](https://timsong-cpp.github.io/cppwp/n4868/temp.constr.order))[.](https://timsong-cpp.github.io/cppwp/n4868/class#special-6.sentence-1) `S<int>` has trivial copy/move constructor and trivial destructor. Its copy assignment operator is not eligible because its constraint is not satisfied. Its move assignment operator is not eligible because it's not declared. Is this a Clang bug?