我尝试从移动捕获lambda表达式创建std::function
。请注意,我可以创建一个移动捕获lambda表达式而不会出现问题;只有当我尝试将其包裹在std::function
中时才会出现错误。
例如:
auto pi = std::make_unique<int>(0);
// no problems here!
auto foo = [q = std::move(pi)] {
*q = 5;
std::cout << *q << std::endl;
};
// All of the attempts below yield:
// "Call to implicitly-deleted copy constructor of '<lambda...."
std::function<void()> bar = foo;
std::function<void()> bar{foo};
std::function<void()> bar{std::move(foo)};
std::function<void()> bar = std::move(foo);
std::function<void()> bar{std::forward<std::function<void()>>(foo)};
std::function<void()> bar = std::forward<std::function<void()>>(foo);
我将解释为什么我要写这样的东西。我编写了一个UI库,类似于jQuery或JavaFX,允许用户通过将std::function
传递给名称为on_mouse_down()
,on_mouse_drag()
的方法来处理鼠标/键盘事件。 ,push_undo_action()
等。
显然,我想要传入的std::function
理想情况下应该使用移动捕获lambda表达式,否则我需要求助于丑陋的&#34;释放/获取在lambda&#34;当C ++ 11成为标准时我正在使用的成语:
std::function<void()> baz = [q = pi.release()] {
std::unique_ptr<int> p{q};
*p = 5;
std::cout << *q << std::endl;
};
请注意,调用baz
两次将是上述代码中的错误。但是,在我的代码中,这个闭包保证只被调用一次。
顺便说一下,在我的真实代码中,我没有传递std::unique_ptr<int>
,而是更有趣的东西。
最后,我使用Xcode6-Beta4,它使用以下版本的clang:
Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)
Target: x86_64-apple-darwin13.3.0
Thread model: posix
答案 0 :(得分:27)
template<class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);
要求:
F
应为CopyConstructible
。对于参数类型f
,Callable
应为ArgTypes
,并返回类型R
。 A的拷贝构造函数和析构函数不会抛出异常。§20.9.11.2.1[func.wrap.func.con]
请注意,operator =
是根据此构造函数和swap
定义的,因此适用相同的限制:
template<class F> function& operator=(F&& f);
效果:
function(std::forward<F>(f)).swap(*this);
§20.9.11.2.1[func.wrap.func.con]
所以回答你的问题:是的,有可能从一个移动捕获lambda构造一个std::function
(因为这只指定了lambda捕获的方式),但它不可以从仅移动类型构建std::function
(例如移动捕获lambda,移动捕获不可复制的内容)。
答案 1 :(得分:24)
由于std::function<?>
必须键入 - 擦除存储的可调用对象的复制构造函数,因此无法从仅移动类型构造它。您的lambda,因为它按值捕获仅移动类型,是一种仅移动类型。所以...你无法解决你的问题。 std::function
无法存储您的lambda。
至少不是直接的。
这是C ++,我们只是解决问题。
template<class F>
struct shared_function {
std::shared_ptr<F> f;
shared_function() = delete; // = default works, but I don't use it
shared_function(F&& f_):f(std::make_shared<F>(std::move(f_))){}
shared_function(shared_function const&)=default;
shared_function(shared_function&&)=default;
shared_function& operator=(shared_function const&)=default;
shared_function& operator=(shared_function&&)=default;
template<class...As>
auto operator()(As&&...as) const {
return (*f)(std::forward<As>(as)...);
}
};
template<class F>
shared_function< std::decay_t<F> > make_shared_function( F&& f ) {
return { std::forward<F>(f) };
}
现在上面已经完成,我们可以解决您的问题。
auto pi = std::make_unique<int>(0);
auto foo = [q = std::move(pi)] {
*q = 5;
std::cout << *q << std::endl;
};
std::function< void() > test = make_shared_function( std::move(foo) );
test(); // prints 5
shared_function
的语义与其他函数略有不同,因为它的副本与原始函数共享相同的状态(包括转换为std::function
时)。
我们还可以编写一个仅移动的一次性函数:
template<class Sig>
struct fire_once;
template<class T>
struct emplace_as {};
template<class R, class...Args>
struct fire_once<R(Args...)> {
// can be default ctored and moved:
fire_once() = default;
fire_once(fire_once&&)=default;
fire_once& operator=(fire_once&&)=default;
// implicitly create from a type that can be compatibly invoked
// and isn't a fire_once itself
template<class F,
std::enable_if_t<!std::is_same<std::decay_t<F>, fire_once>{}, int> =0,
std::enable_if_t<
std::is_convertible<std::result_of_t<std::decay_t<F>&(Args...)>, R>{}
|| std::is_same<R, void>{},
int
> =0
>
fire_once( F&& f ):
fire_once( emplace_as<std::decay_t<F>>{}, std::forward<F>(f) )
{}
// emplacement construct using the emplace_as tag type:
template<class F, class...FArgs>
fire_once( emplace_as<F>, FArgs&&...fargs ) {
rebind<F>(std::forward<FArgs>(fargs)...);
}
// invoke in the case where R is not void:
template<class R2=R,
std::enable_if_t<!std::is_same<R2, void>{}, int> = 0
>
R2 operator()(Args...args)&&{
try {
R2 ret = invoke( ptr.get(), std::forward<Args>(args)... );
clear();
return ret;
} catch(...) {
clear();
throw;
}
}
// invoke in the case where R is void:
template<class R2=R,
std::enable_if_t<std::is_same<R2, void>{}, int> = 0
>
R2 operator()(Args...args)&&{
try {
invoke( ptr.get(), std::forward<Args>(args)... );
clear();
} catch(...) {
clear();
throw;
}
}
// empty the fire_once:
void clear() {
invoke = nullptr;
ptr.reset();
}
// test if it is non-empty:
explicit operator bool()const{return (bool)ptr;}
// change what the fire_once contains:
template<class F, class...FArgs>
void rebind( FArgs&&... fargs ) {
clear();
auto pf = std::make_unique<F>(std::forward<FArgs>(fargs)...);
invoke = +[](void* pf, Args...args)->R {
return (*(F*)pf)(std::forward<Args>(args)...);
};
ptr = {
pf.release(),
[](void* pf){
delete (F*)(pf);
}
};
}
private:
// storage. A unique pointer with deleter
// and an invoker function pointer:
std::unique_ptr<void, void(*)(void*)> ptr{nullptr, +[](void*){}};
void(*invoke)(void*, Args...) = nullptr;
};
通过emplace_as<T>
标记支持甚至不可移动的类型。
请注意,您必须在右值上下文中评估()
(即在std::move
之后),因为无声的破坏性()
似乎很粗鲁。
这个实现不使用SBO,因为如果它确实存在,它会要求存储的类型是可移动的,并且(对我来说)启动会更有效。
答案 2 :(得分:0)
这是一个更简单的解决方案:
auto pi = std::make_unique<int>(0);
auto ppi = std::make_shared<std::unique_ptr<int>>(std::move(pi));
std::function<void()> bar = [ppi] {
**ppi = 5;
std::cout << **ppi << std::endl;
};