可变数据成员、模板构造函数和简单复制可构造

mutable data member, template constructor and trivially copy constructible

提问人:wanghan02 提问时间:6/28/2023 最后编辑:Jan Schultkewanghan02 更新时间:6/28/2023 访问量:161

问:

示例代码可以在下面或 godbolt 上找到。假设我们有 4 个类:

  1. S<T>:持有数据成员。

  2. SCtor<T>:保存数据成员并具有模板构造函数。

  3. SCtorMutable<T>:保存可变数据成员并具有模板构造函数。

  4. SCtorDefault<T>:持有成员,具有模板构造函数,具有默认的复制/移动构造函数和默认的复制/移动赋值运算符。

所有编译器都同意这 4 个类是可复制的。

如果有一个简单的包装类将上述任何类作为数据成员保存。包装类仍然很容易复制。W<T>W<S...<T>>

如果有另一个包装类将上述任何一个类作为可变数据成员保存。WMutable<T>

  1. MSVC 仍然认为是可以复制的。WMutable<S...<T>>
  2. Clang认为是可以复制的。 不是平凡的复制可构造的,因此不是平凡的可复制的。WMutable<S<T>>WMutable<SCtor...<T>>
  3. GCC认为是可以复制的。 不是简单的复制可构造的,而是简单的可复制的。WMutable<S<T>>WMutable<SCtor...<T>>

应该很容易复制吗?WMutable<T>

#include <type_traits>
#include <utility>

template<typename T>
struct S {
    T m_t;
};

template<typename T>
struct SCtor {
    T m_t;
    template<typename... U>
    SCtor(U&&... u): m_t(std::forward<U>(u)...) {}
};

template<typename T>
struct SCtorMutable {
    mutable T m_t;
    template<typename... U>
    SCtorMutable(U&&... u): m_t(std::forward<U>(u)...) {}
};

template<typename T>
struct SCtorDefault {
    T m_t;
    template<typename... U>
    SCtorDefault(U&&... u): m_t(std::forward<U>(u)...) {}
    SCtorDefault(SCtorDefault const&) = default;
    SCtorDefault(SCtorDefault&&) = default;
    SCtorDefault& operator=(SCtorDefault const&) = default;
    SCtorDefault& operator=(SCtorDefault&&) = default;
};

template<typename T>
struct W {
    T m_t;
};

template<typename T>
struct WMutable {
    mutable T m_t;
};

static_assert(std::is_trivially_copyable<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_trivially_copy_assignable<S<int>>::value);
static_assert(std::is_trivially_move_assignable<S<int>>::value);

static_assert(std::is_trivially_copyable<SCtor<int>>::value);
static_assert(std::is_trivially_copy_constructible<SCtor<int>>::value);
static_assert(std::is_trivially_move_constructible<SCtor<int>>::value);
static_assert(std::is_trivially_copy_assignable<SCtor<int>>::value);
static_assert(std::is_trivially_move_assignable<SCtor<int>>::value);

static_assert(std::is_trivially_copyable<SCtorMutable<int>>::value);
static_assert(std::is_trivially_copy_constructible<SCtorMutable<int>>::value);
static_assert(std::is_trivially_move_constructible<SCtorMutable<int>>::value);
static_assert(std::is_trivially_copy_assignable<SCtorMutable<int>>::value);
static_assert(std::is_trivially_move_assignable<SCtorMutable<int>>::value);

static_assert(std::is_trivially_copyable<SCtorDefault<int>>::value);
static_assert(std::is_trivially_copy_constructible<SCtorDefault<int>>::value);
static_assert(std::is_trivially_move_constructible<SCtorDefault<int>>::value);
static_assert(std::is_trivially_copy_assignable<SCtorDefault<int>>::value);
static_assert(std::is_trivially_move_assignable<SCtorDefault<int>>::value);

static_assert(std::is_trivially_copyable<W<S<int>>>::value);
static_assert(std::is_trivially_copy_constructible<W<S<int>>>::value);
static_assert(std::is_trivially_move_constructible<W<S<int>>>::value);
static_assert(std::is_trivially_copy_assignable<W<S<int>>>::value);
static_assert(std::is_trivially_move_assignable<W<S<int>>>::value);

static_assert(std::is_trivially_copyable<W<SCtor<int>>>::value);
static_assert(std::is_trivially_copy_constructible<W<SCtor<int>>>::value);
static_assert(std::is_trivially_move_constructible<W<SCtor<int>>>::value);
static_assert(std::is_trivially_copy_assignable<W<SCtor<int>>>::value);
static_assert(std::is_trivially_move_assignable<W<SCtor<int>>>::value);

static_assert(std::is_trivially_copyable<W<SCtorMutable<int>>>::value);
static_assert(std::is_trivially_copy_constructible<W<SCtorMutable<int>>>::value);
static_assert(std::is_trivially_move_constructible<W<SCtorMutable<int>>>::value);
static_assert(std::is_trivially_copy_assignable<W<SCtorMutable<int>>>::value);
static_assert(std::is_trivially_move_assignable<W<SCtorMutable<int>>>::value);

static_assert(std::is_trivially_copyable<W<SCtorDefault<int>>>::value);
static_assert(std::is_trivially_copy_constructible<W<SCtorDefault<int>>>::value);
static_assert(std::is_trivially_move_constructible<W<SCtorDefault<int>>>::value);
static_assert(std::is_trivially_copy_assignable<W<SCtorDefault<int>>>::value);
static_assert(std::is_trivially_move_assignable<W<SCtorDefault<int>>>::value);

static_assert(std::is_trivially_copyable<WMutable<S<int>>>::value);
static_assert(std::is_trivially_copy_constructible<WMutable<S<int>>>::value);
static_assert(std::is_trivially_move_constructible<WMutable<S<int>>>::value);
static_assert(std::is_trivially_copy_assignable<WMutable<S<int>>>::value);
static_assert(std::is_trivially_move_assignable<WMutable<S<int>>>::value);

static_assert(std::is_trivially_copyable<WMutable<SCtor<int>>>::value); // error with clang
static_assert(std::is_trivially_copy_constructible<WMutable<SCtor<int>>>::value); // error with clang/gcc
static_assert(std::is_trivially_move_constructible<WMutable<SCtor<int>>>::value);
static_assert(std::is_trivially_copy_assignable<WMutable<SCtor<int>>>::value);
static_assert(std::is_trivially_move_assignable<WMutable<SCtor<int>>>::value);

static_assert(std::is_trivially_copyable<WMutable<SCtorMutable<int>>>::value); // error with clang
static_assert(std::is_trivially_copy_constructible<WMutable<SCtorMutable<int>>>::value); // error with clang/gcc
static_assert(std::is_trivially_move_constructible<WMutable<SCtorMutable<int>>>::value);
static_assert(std::is_trivially_copy_assignable<WMutable<SCtorMutable<int>>>::value);
static_assert(std::is_trivially_move_assignable<WMutable<SCtorMutable<int>>>::value);

static_assert(std::is_trivially_copyable<WMutable<SCtorDefault<int>>>::value); // error with clang
static_assert(std::is_trivially_copy_constructible<WMutable<SCtorDefault<int>>>::value); // error with clang/gcc
static_assert(std::is_trivially_move_constructible<WMutable<SCtorDefault<int>>>::value);
static_assert(std::is_trivially_copy_assignable<WMutable<SCtorDefault<int>>>::value);
static_assert(std::is_trivially_move_assignable<WMutable<SCtorDefault<int>>>::value);
C++ 语言律师 复制构造函数 mutable-trivially-copyable

评论

0赞 sweenish 6/28/2023
您可能需要添加 language-lawyer 标签。我从这个页面找到了一些指南。我不是语言律师,但在某些时候,各种嵌套将答案更改为“视情况而定”似乎并不夸张。

答:

2赞 maxplus 6/28/2023 #1
  • 简单复制是可构建的吗?WMutable<SCtor...<T>>

不。一般来说,它根本不是可复制的。它的隐式复制构造函数尝试从非常量引用复制构造,因为复制自是可变的。非用户提供的复制构造函数 of (默认为 for 并隐式声明为 , ) 接受常量引用,因此选择可变参数模板通用引用构造函数。即使它可以构造自 ,它也是用户提供的。WMutable::m_tWMutable::m_tSCtor...<T>SCtorDefaultSCtorSCtorMutableTSCtor...<T>

类 X 的复制/移动构造函数不是用户提供的,则它是微不足道的,其声明的参数类型是 就好像它被隐含地声明了一样,如果
......
— 对于类类型(或其数组)的 X 的每个非静态数据成员,选择复制/移动该成员的构造函数是微不足道的;

  • 可以轻易复制吗?WMutable<SCtor...<T>>

不,它有一个不平凡的复制构造函数。

一个可复制的类是这样的类:
— 没有非平凡的复制构造函数 (12.8),
...

因此,满足一个可复制类的所有条件,因为它(显然)取决于,并且所有类都不是可复制的。WMutable<S<int>>WMutable<S<T>>WMutable<SCtor...<T>>

4赞 Jan Schultke 6/28/2023 #2

Clang 是三个编译器中唯一正确的。简短的回答是,向数据成员添加会导致非平凡的可变参数构造函数在重载解析方面胜过平凡的、隐式定义的复制构造函数。这发生在 的复制构造函数中,因此不是简单的可复制的。mutableWMutableWMutable

长答案

通常的作用是:mutable

  • const 对象是 const 对象的类型对象或不可变子对象。const T
  • [...]

- https://eel.is/c++draft/basic.type.qualifier#1

这意味着我们的数据成员不是 ,这会影响重载解决。 让我们考虑一下类型扩展为什么:SCtor<int>constconst WMutable<SCtor<int>>>

struct const WMutable<SCtor<int>> {
    SCtor<int> m_t;

    // implicitly declared and defined, not actually defaulted
    const_WMutable_SCtor_int(const const_WMutable_SCtor_int&) = default;
    // ...
};

隐式定义或显式默认的复制构造函数复制每个成员。复制成员不一定使用复制构造函数:

[...]否则,将使用 的相应 base 或成员直接初始化 base 或成员。x

- https://eel.is/c++draft/class.copy.ctor#14

这意味着我们得到了以下内容:

// if this was defined by the compiler, it would look like ...
const_WMutable_SCtor_int(const const_WMutable_SCtor_int& other)
  : m_t(other.m_t) {}

m_t将初始化为 (lvalue) 类型的参数,并且可以调用两个构造函数:SCtor<int>

// (1) this constructor is implicitly declared and defined for SCtor<int>
SCtor(const SCtor&)

// (2) this constructor is user-defined
template<typename... U>
SCtor(U&&... u): m_t(std::forward<U>(u)...) {}

构造函数 (2) 在重载解析中胜出,因为从 (lvalue) 到 & 的转换序列比 to 短。SCtor<int>SCtor<int>const SCtoer<int>&

因此,类型(以及示例中的其他专用化)不是可复制的,因为它违反了要求:WMutable<SCtor<int>>WMutable

[...]其中,每个符合条件的复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符都是微不足道的,并且

- https://eel.is/c++draft/class.prop#1

的复制构造函数不是微不足道的,因此该类不是可复制的,也不是简单的复制构造的。WMutable<SCtoer<int>>

GCC 和 MSVC 错误

GCC 和 MSVC 必须错误地将重载集限制为仅复制构造函数,而不是可用于复制成员的其他构造函数。 重现此错误的最短方法是:

#include <type_traits>

struct test {
    int member;
    template <typename T>
    test(T&); // not a copy constructor
};

// every compiler agrees and complies, this should pass
static_assert(std::is_trivially_copy_constructible_v<test>);
static_assert(std::is_trivially_copyable_v<test>);

struct wrapper {
    mutable test member;
};

// both should fail, but MSVC allows both due to not considering
// test<T>(T&) as part of the overload set, only its copy constructors
static_assert(std::is_trivially_copy_constructible_v<wrapper>);
static_assert(std::is_trivially_copyable_v<wrapper>);

查看 Compiler Explorer 上的实时示例

然而,对于这个更简单的例子,GCC 和 Clang 同意。只有 MSVC 不合规(未更改)。/permissive-