为什么 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
- 这也会将右值转换为右值引用并期望成为能够移动参数。
在下面的代码中:
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 typesArgTypes...
and return typeR
.[func.wrap.func]/2 A callable type (20.14.2)
F
is Lvalue-Callable for argument typesArgTypes
and return typeR
if the expressionINVOKE<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
- 这也会将右值转换为右值引用并期望成为能够移动参数。