为什么 bind_front/bind_back/not_fn/bind 需要 Args...是可移动的?

Why does bind_front/bind_back/not_fn/bind require Args... to be move-constructible?

提问人:康桓瑋 提问时间:9/24/2023 最后编辑:Nicol Bolas康桓瑋 更新时间:9/24/2023 访问量:86

问:

我注意到产生完美转发调用包装器的 // 都要求传入的函数参数和参数参数必须是可移动构造的。std::bind_frontstd::bind_backstd::not_fn

std::bind_front 的标准规范为例:

template<class F, class... Args>
  constexpr unspecified bind_front(F&& f, Args&&... args);

[...]

任务:

  is_constructible_v<FD, F> &&
  is_move_constructible_v<FD> &&
  (is_constructible_v<BoundArgs, Args> && ...) &&
  (is_move_constructible_v<BoundArgs> && ...)

是。true

其中 是类型 ,并且是表示 的包。FDdecay_t<F>BoundArgsdecay_t<Args>...

此外,旧版具有前提条件,并且必须满足 [func.bind.bind] 中的 Cpp17MoveConstructible 要求。std::bindFDBoundArgs

我可以理解这部分,因为我们必须能够将参数转发到包装器内的衰减副本。但让我感到困惑的是,为什么我们需要要求这些参数是可移动构造的?is_constructible_v

我最初的猜测是,这是为了让完美的转发调用包装器也可移动构造,因为该标准要求调用包装器必须满足 [func.require] 的 Cpp17MoveConstructible 和 Cpp17Destructible 要求。

但是,这似乎是错误的,因为如果该对象具有有效的复制构造函数,则包装非可移动对象的包装器仍然可以移动构造:

struct OnlyCopyable {
  OnlyCopyable(const OnlyCopyable&) = default;
  OnlyCopyable(OnlyCopyable&&) = delete;
};

struct Wrapper {
  OnlyCopyable copy1;
  std::tuple<OnlyCopyable> copy2;
};

static_assert(!std::move_constructible<OnlyCopyable>); 
static_assert( std::move_constructible<Wrapper>); // ok

事实证明,这种额外的约束使标准拒绝以下内容

#include <functional>

struct OnlyCopyable {
  OnlyCopyable() = default;
  OnlyCopyable(const OnlyCopyable&) = default;
  OnlyCopyable(OnlyCopyable&&) = delete;
};

struct OnlyCopyableFun {
  OnlyCopyableFun() = default;
  OnlyCopyableFun(const OnlyCopyableFun&) = default;
  OnlyCopyableFun(OnlyCopyableFun&&) = delete;
  int operator()(auto) const;
};

int main() {
  OnlyCopyable arg;
  auto fun1 = std::bind_front([](auto) { }, arg);  // ill-formed
  OnlyCopyableFun fun;
  auto fun2 = std::bind_front(OnlyCopyableFun, 0); // ill-formed
}

在我看来,这不应该是因为我没有看到拒绝上述内容的好处。

那么,为什么标准要求传递到调用包装器工厂的参数类型必须是可移动构造的呢?这背后的原理是什么?

C++ 移动构造函数 stdbind c++23

评论

2赞 Red.Wave 9/24/2023
真正的问题是:如果复制构造函数应该存在,为什么要删除移动构造函数? 您使用的这种模式是什么意思?通常,我们要么需要内容无法传输给对等方的对象(不可移动,不可复制),要么需要内容应通过单个句柄查看的对象(仅移动),或者其内容可以被对等方复制的对象(可复制);最后一个类别可能会将 move 声明为函数边界上的优化。你的奇怪模式与任何理性用例都不匹配。明智的做法是问为什么允许这种模式?

答:

2赞 Nicol Bolas 9/24/2023 #1

“只能复制”的类型不是 C++ 标准认可的有效、合理的代码。你可以这样做,但就标准而言,这种类型在概念上是被破坏的。因此,标准库类型的许多功能可能不适用于此类类型。

从概念上讲,移动被视为副本的专用版本,在某些情况下取代复制。这使得 move 成为副本的子集。大多数标准都是这样处理的:可以复制的类型也可以移动。

同样,从技术上讲,你可以制作一个不能像这样工作的类型,但大多数标准不会对你感到满意。例如,你不能把它们放进去。std::vector<T>

评论

1赞 康桓瑋 9/24/2023
谢谢,这可能是我正在寻找的答案。这种不合理的类型也可能涉及带有 或 的类型。我想知道标准是否在任何地方都谈到了这种“不快乐”的类型,尽管我对此表示怀疑。explicit T(const T&)T(T&)
1赞 ildjarn 9/27/2023
事实上,这个想法在 std::copy_constructible 中已经正式化了,而错误是标准必须说的。;-] (即,就标准而言,OP 的“可复制”概念并不是“可复制”的实际含义)std::copy_constructible<OnlyCopyable>