为什么'std::p air<int, movable>'需要一个[已删除的]'const&'复制构造函数?

Why does `std::pair<int, movable>` require a [deleted] `const&` copy constructor?

本文关键字:删除 一个 复制 构造函数 const lt int air std movable 为什么      更新时间:2023-10-16

我正忙于测试各种通用算法的实现,并且我正在使用对所提供函数支持最少的类型。当使用具有某种类型T(例如,int)的std::pair<T, movable>和定义如下的movable类型时,我遇到了这种奇怪的设置:

struct movable
{
    movable() {}
    movable(movable&&) = default;
    // movable(movable const&) = delete;
    movable(movable&) = delete;
};

这个想法是有一个可移动但不可复制的类型。这很好用,例如,对于这样的表达式:

movable m1 = movable();
movable m2 = std::move(m1);

但是,当尝试将此类型用作std::pair<...>的成员时,它会失败!要获取要编译的代码,有必要添加采用movable const&(或仅具有该版本)的 delete d(!) 复制构造函数。采用非const引用的复制构造函数是不够的:

#include <utility>
auto f() -> std::pair<int, movable> {
    return std::pair<int, movable>(int(), movable());
}

这是怎么回事?std::pair<...>强制要求std::pair(std::pair const&) = default是不是过度指定了?

问题似乎归结为std::pair的复制构造函数的规范(在 20.3.2 [pairs.pair] 概要中):

 namespace std {
     template <class T1, class T2>
     struct pair {
         ...
         pair(const pair&) = default;
         ...
     };
 }

快速检查我的实现意味着复制两个成员的明显实现不需要movable复制构造函数的const&版本。也就是说,攻击部分是pair复制构造函数上的= default

std::pair复制构造函数声明如下:

pair(const pair&) = default;

通过声明此复制构造函数movable

movable(movable&) = delete;

你禁止隐式创建movable(const movable&)(所以它甚至没有被删除,只是没有这样的构造函数),因此这是你唯一的复制构造函数。但是std::pair复制构造函数需要其成员的复制构造函数来引用 const 引用,因此会出现编译错误。

如果添加以下内容:

movable(movable const&) = delete;

或者(最好)只是删除movable(movable&) = delete;声明,您现在有了movable(movable const&)构造函数,并且由于它被删除,std::pair复制构造函数也被删除。

更新:让我们考虑一个更简单的示例来演示相同的问题。这不会编译:

template <typename T>
struct holder {
    T t;
    // will compile if you comment the next line
    holder(holder const&) = default;
    // adding or removing move constructor changes nothing WRT compile errors
    // holder(holder&&) = default;
};
struct movable {
    movable() {}
    movable(movable&&) = default;
    // will also compile if you uncomment the next line
    //movable(movable const&) = delete;
    movable(movable&) = delete;
};
holder<movable> h{movable()};

如果您注释 holder 的复制构造函数,它将编译,因为这就是隐式复制构造函数生成的工作方式([class.copy]/8

类 X 的隐式声明的复制构造函数将具有以下形式

X::X(const X&)

如果类类型的每个潜在构造子对象M(或其数组)都有一个复制构造函数,其第一个参数的类型为 const M&const volatile M& .否则,隐式声明的复制构造函数将具有

X::X(X&)

也就是说,当您注释掉声明holder(holder const&) = default;隐式声明的复制构造函数时,holder 的格式将具有 holder(holder&) .但是如果你不这样做,T的复制构造函数已经const T&(或const volatile T&),因为这将在[class.copy]/15中描述的成员复制过程中调用。

如果holder有一个移动构造函数,那就更容易了 - 如果你注释掉holder(holder const&) = default;,隐式声明的holder复制构造函数将被删除。