为什么 class 需要移动操作来绑定到 std::function,而 std::function 的签名中 class 是按值传递的?

Why would a class need move operations to bind to a std::function that has a signature in which the class is passed by value?

在下面的代码中:

struct X
{
    X() = default;
    X(const X&) { printf("copy construct\n"); }
    X& operator=(const X&) { printf("copy assign\n"); return *this; }
    X(X&&) { printf("move construct\n"); }
    X& operator=(X&&) { printf("move assign\n"); return *this; }
// Replacing the above two lines with these lines below causes a compile error.
//    X(X&&) = delete;
//    X& operator=(X&&) = delete;
};

void f(X x) {}

int main()
{
    X x;
    std::function<void (X)> fx(f);
    f(x);

    return 0;
}

如果我将结构 X 定义为具有复制和移动操作,那么带有签名 void (X) 的 std::function 能够绑定到它。但是,如果我删除移动操作,代码将不再编译,并出现此错误:

prog.cc:26:29: error: no matching constructor for initialization of 'std::function<void (X)>'
    std::function<void (X)> fx(f);
 candidate template ignored: requirement '__callable<void (*&)(X), false>::value' was not satisfied [with _Fp = void (*)(X)]
    function(_Fp);

我只是想了解,如果签名描述了 X 按值传递的函数,为什么需要移动操作?

std::function<void (X)> fx(f) 构造函数调用格式错误。

首先,对这个构造函数的要求:

[func.wrap.func.con]
template<class F> function(F f);
7 Constraints: F is Lvalue-Callable (20.14.16.2) for argument types ArgTypes... and return type R.

[func.wrap.func]/2 A callable type (20.14.2) F is Lvalue-Callable for argument types ArgTypes and return type R if the expression INVOKE<R>(declval<F&>(), declval<ArgTypes>()...), considered as an unevaluated operand (7.2), is well-formed (20.14.3).

我相信 f 实际上不是参数类型 X 的 Lvalue-Callable,尽管这听起来很奇怪。这取决于 declval:

的定义

[declval]
template<class T> add_rvalue_reference_t<T> declval() noexcept;

所以,declval<X>()的类型实际上是X&&,而不是X。调用 f(declval<X>()) 需要从这个右值引用移动到按值参数 - 但移动构造函数被声明为已删除。事实上,sizeof(f(std::declval<X>()), 0); fails to compile,也抱怨删除的移动构造函数。

换句话说,std::function<void (X)> fx(f) 的格式错误与 X x; f(std::move(x)); 格式错误的原因基本相同。


实际上,std::function::operator() 需要能够将其参数转发给包装的可调用对象,并为此使用 std::forward - 这也会将右值转换为右值引用并期望成为能够移动参数。