为什么`std :: pair <int,movable>`需要一个[deleted]`const&`copy constructor?

Die*_*ühl 18 c++ stl c++-standard-library c++11

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

struct movable
{
    movable() {}
    movable(movable&&) = default;
    // movable(movable const&) = delete;
    movable(movable&) = delete;
};
Run Code Online (Sandbox Code Playgroud)

这个想法有一种可移动但不可复制的类型.这很好用,例如,使用这样的表达式:

movable m1 = movable();
movable m2 = std::move(m1);
Run Code Online (Sandbox Code Playgroud)

但是,当尝试使用此类型作为其成员std::pair<...>时失败!为了使代码得到编译,有必要添加deleted(!)复制构造函数来获取movable const&(或只有该版本).采用非const引用的复制构造函数是不够的:

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

这里发生了什么?是std::pair<...>通过强制要求overspecified 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;
         ...
     };
 }
Run Code Online (Sandbox Code Playgroud)

快速检查我的执行意味着明显实现复制两个成员并没有要求const&该版本movable的拷贝构造函数.也就是说,攻击部分是= defaulton pair的复制构造函数!

Ant*_*vin 8

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

pair(const pair&) = default;
Run Code Online (Sandbox Code Playgroud)

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

movable(movable&) = delete;
Run Code Online (Sandbox Code Playgroud)

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

如果你添加这个:

movable(movable const&) = delete;
Run Code Online (Sandbox Code Playgroud)

或者(更好)只是删除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()};
Run Code Online (Sandbox Code Playgroud)

如果你注释复制构造函数,它将编译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将被删除.

  • 好吧,你的`holder`显然不是可移动构造的,因为它声明了一个复制构造函数,从而禁止自动创建的移动构造函数,但也没有声明一个移动构造.`std :: pair`定义了一个移动构造函数(它也是`= default`ed).我仍然不想复制我的对象,只是移动它. (2认同)