Class isn't trivially_copyable if a constraint on its assignment operator is not satisfied with clang 16

huangapple go评论61阅读模式
英文:

Class isn't trivially_copyable if a constraint on its assignment operator is not satisfied with clang 16

问题

以下是翻译好的内容:

示例代码如下或在[godbolt][1]上查看。clang 16/trunk认为`S<int>`不是一个`trivially_copyable`类。而clang 15、gcc trunk和MSVC则持相反观点。

#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;
};

// 下面的五个断言对所有编译器都通过
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);

// 在gcc trunk、MSVC和clang 15上编译通过,在clang 16/trunk上失败
static_assert(std::is_trivially_copyable<S<int>>::value);

根据标准[class.prop][2]:

> 一个trivially copyable类是一个类:
>
> - 至少具有一个合格的复制构造函数、移动构造函数、复制赋值运算符或移动赋值运算符([special]、[class.copy.ctor]、[class.copy.assign]),
>
> - 每个合格的复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符都是trivial的,且
>
> - 具有一个trivial、非删除的析构函数([class.dtor])。

`S<int>`具有trivial复制/移动构造函数和trivial析构函数。它的复制/移动赋值运算符不合格。我同意gcc/MSVC/clang15的观点。clang 16/trunk是否有错或我漏掉了什么?

编辑:这是一个已确认的[clang错误][3]。

  [1]: https://godbolt.org/z/dzorGb68o
  [2]: https://timsong-cpp.github.io/cppwp/n4868/class.prop#1
  [3]: https://github.com/llvm/llvm-project/issues/63352

注意:我已经删除了代码部分,只提供了翻译的文本。如果您需要更多翻译,请提供具体的文本部分。

英文:

Example 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 class.prop:

> A trivially copyable class is a class:
>
> - that has at least one eligible copy constructor, move constructor, copy assignment operator, or move assignment operator ([special],
> [class.copy.ctor], [class.copy.assign]),
>
> - where each eligible copy constructor, move constructor, copy assignment operator, and move assignment operator is trivial, and
>
> - that has a trivial, non-deleted destructor ([class.dtor]).

S<int> has trivial copy/move constructor and trivial destructor. Its copy/move assignment operators are not eligible. I would agree with gcc/MSVC/clang15 on this. Is clang 16/trunk wrong on this one or am I missing something?

Edit: This is a confirmed clang bug.

答案1

得分: 1

是的,S<int> 是可以轻松复制的。看起来你发现了一个 clang 的 bug。我在以下地方都找不到它:

所以这可能是一个新的退化情况。

S<int> 是可以轻松复制的证明

值得考虑 elegible 的含义:

> 一个符合条件的特殊成员函数是指满足以下条件的特殊成员函数:
> - 函数没有被删除,
> - **关联的约束([temp.constr]),如果有的话,被满足,
> - 同一种特殊成员函数没有更多的约束([temp.constr.order])。

- [class.prop] §1

S& operator=(S const&) requires (!std::is_integral<T>::value) = default

这个复制赋值操作符是用户声明的(而不是用户提供的),它的约束条件没有被满足,所以它不符合条件。此外:

> 如果类 X 的定义没有显式声明移动赋值操作符,那么只有在以下条件下才会隐式声明为默认值,即
> - X 没有用户声明的复制构造函数,
> - [...]

- [class.copy.assign] $4

这意味着:

  • 复制赋值操作符是用户声明的,但不符合条件
  • 移动赋值操作符未声明

所有剩下的(符合条件的)特殊成员函数都是平凡的,因此 S<int> 是可以轻松复制的。

英文:

Yes, S&lt;int&gt; is trivially copyable. It looks like you've discovered a clang bug. I was unable to find it in:

So this may be a new regression.

Proof for S&lt;int&gt; being trivially copyable

It's worth examining what elegible means:

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

- [class.prop] §1

S&amp; operator=(S const&amp;) requires (!std::is_integral&lt;T&gt;::value) = default

This copy assignment operator is user-declared (and not user-provided) and its constraints aren't satisfied, so it is not eligible. Furthermore:

> If the definition of a class X does not explicitly declare a move assignment operator, one will be implicitly declared as defaulted if and only if
> - X does not have a user-declared copy constructor,
> - [...]

- [class.copy.assign] $4

This means that:

  • the copy-assignment operator is user-declared, but not eligible
  • the move-assignment operator is not declared

All of the remaining (eligible) special member functions are trivial, therefore S&lt;int&gt; is trivially copyable.

huangapple
  • 本文由 发表于 2023年6月15日 20:32:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/76482516.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定