我需要将带有已删除的复制构造函数的结构绑定到函数中。我已经减少了我想要实现的最小例子:
struct Bar {
int i;
Bar() = default;
Bar(Bar&&) = default;
Bar(const Bar&) = delete;
Bar& operator=(const Bar&) = delete;
};
void foo(Bar b) {
std::cout << b.i << std::endl;
}
int main()
{
Bar b;
b.i = 10;
std::function<void()> a = std::bind(foo, std::move(b)); // ERROR
a();
return 0;
}
从编译器中我只会哭泣和咬牙切齿:
test.cpp:22:27: error: no viable conversion from 'typename _Bind_helper<__is_socketlike<void (&)(Bar)>::value, void (&)(Bar), Bar>::type' (aka '_Bind<__func_type (typename decay<Bar>::type)>') to 'std::function<void ()>'
std::function<void()> a = std::bind(foo, std::move(b));
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.1.0/../../../../include/c++/5.1.0/functional:2013:7: note: candidate constructor not viable: no known conversion from 'typename _Bind_helper<__is_socketlike<void (&)(Bar)>::value, void (&)(Bar),
Bar>::type' (aka '_Bind<__func_type (typename decay<Bar>::type)>') to 'nullptr_t' for 1st argument
function(nullptr_t) noexcept
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.1.0/../../../../include/c++/5.1.0/functional:2024:7: note: candidate constructor not viable: no known conversion from 'typename _Bind_helper<__is_socketlike<void (&)(Bar)>::value, void (&)(Bar),
Bar>::type' (aka '_Bind<__func_type (typename decay<Bar>::type)>') to 'const std::function<void ()> &' for 1st argument
function(const function& __x);
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.1.0/../../../../include/c++/5.1.0/functional:2033:7: note: candidate constructor not viable: no known conversion from 'typename _Bind_helper<__is_socketlike<void (&)(Bar)>::value, void (&)(Bar),
Bar>::type' (aka '_Bind<__func_type (typename decay<Bar>::type)>') to 'std::function<void ()> &&' for 1st argument
function(function&& __x) : _Function_base()
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.1.0/../../../../include/c++/5.1.0/functional:2058:2: note: candidate template ignored: substitution failure [with _Functor = std::_Bind<void (*(Bar))(Bar)>]: no matching function for call to object of
type 'std::_Bind<void (*(Bar))(Bar)>'
function(_Functor);
^
1 error generated.
所以我想问一下是否有任何解决方法可以让我将Bar绑定到foo,同时保持Bar只移动。
修改
还要考虑以下代码,其中变量b
的生命在调用a
之前结束:
int main()
{
std::function<void()> a;
{
Bar b;
b.i = 10;
a = std::bind(foo, std::move(b)); // ERROR
}
a();
return 0;
}
答案 0 :(得分:6)
std::function
无法使用仅限移动的可启用内容。它会删除传入的类型以调用(带签名),销毁和复制。 1
编写仅限移动std::function
只是一些工作。 Here is a stab at it在不同的背景下。 live example
std::packaged_task
有趣的也是一个只移动的类型橡皮擦调用者,但它比你想要的更重,并且获得价值是一种痛苦。
更简单的解决方案是滥用共享指针:
template<class F>
auto shared_function( F&& f ) {
auto pf = std::make_shared<std::decay_t<F>>(std::forward<F>(f));
return [pf](auto&&... args){
return (*pf)(decltype(args)(args)...);
};
}
将一些可调用对象包装到一个共享指针中,将它放在一个lambda完美转发lambda中。
这说明了一个问题 - 通话不起作用!以上所有都有const
调用。
您想要的是一项只能调用的任务。
template<class Sig>
struct task_once;
namespace details_task_once {
template<class Sig>
struct ipimpl;
template<class R, class...Args>
struct ipimpl<R(Args...)> {
virtual ~ipimpl() {}
virtual R invoke(Args&&...args) && = 0;
};
template<class Sig, class F>
struct pimpl;
template<class R, class...Args, class F>
struct pimpl<R(Args...), F>:ipimpl<R(Args...)> {
F f;
template<class Fin>
pimpl(Fin&&fin):f(std::forward<Fin>(fin)){}
R invoke(Args&&...args) && final override {
return std::forward<F>(f)(std::forward<Args>(args)...);
};
};
// void case, we don't care about what f returns:
template<class...Args, class F>
struct pimpl<void(Args...), F>:ipimpl<void(Args...)> {
F f;
template<class Fin>
pimpl(Fin&&fin):f(std::forward<Fin>(fin)){}
void invoke(Args&&...args) && final override {
std::forward<F>(f)(std::forward<Args>(args)...);
};
};
}
template<class R, class...Args>
struct task_once<R(Args...)> {
task_once(task_once&&)=default;
task_once&operator=(task_once&&)=default;
task_once()=default;
explicit operator bool() const { return static_cast<bool>(pimpl); }
R operator()(Args...args) && {
auto tmp = std::move(pimpl);
return std::move(*tmp).invoke(std::forward<Args>(args)...);
}
// if we can be called with the signature, use this:
template<class F,
class R2=R,
std::enable_if_t<
std::is_convertible<std::result_of_t<F&&(Args...)>,R2>{}
&& !std::is_same<R2, void>{}
>* = nullptr
>
task_once(F&& f):task_once(std::forward<F>(f), std::is_convertible<F&,bool>{}) {}
// the case where we are a void return type, we don't
// care what the return type of F is, just that we can call it:
template<class F,
class R2=R,
class=std::result_of_t<F&&(Args...)>,
std::enable_if_t<std::is_same<R2, void>{}>* = nullptr
>
task_once(F&& f):task_once(std::forward<F>(f), std::is_convertible<F&,bool>{}) {}
// this helps with overload resolution in some cases:
task_once( R(*pf)(Args...) ):task_once(pf, std::true_type{}) {}
// = nullptr support:
task_once( std::nullptr_t ):task_once() {}
private:
std::unique_ptr< details_task_once::ipimpl<R(Args...)> > pimpl;
// build a pimpl from F. All ctors get here, or to task() eventually:
template<class F>
task_once( F&& f, std::false_type /* needs a test? No! */ ):
pimpl( new details_task_once::pimpl<R(Args...), std::decay_t<F>>{ std::forward<F>(f) } )
{}
// cast incoming to bool, if it works, construct, otherwise
// we should be empty:
// move-constructs, because we need to run-time dispatch between two ctors.
// if we pass the test, dispatch to task(?, false_type) (no test needed)
// if we fail the test, dispatch to task() (empty task).
template<class F>
task_once( F&& f, std::true_type /* needs a test? Yes! */ ):
task_once( f?task_once( std::forward<F>(f), std::false_type{} ):task_once() )
{}
};
请注意,您只能使用上述()
在右值上下文中调用task_once
。这是因为()
具有破坏性,因为它应该是你的情况。
可悲的是,上面依赖于C ++ 14。我现在不喜欢编写C ++ 11代码。所以,这是一个更简单的C ++ 11解决方案,性能较差:
std::function<void()> a;
{
Bar b;
b.i = 10;
auto pb = std::make_shared<Bar>(std::move(b));
a = [pb]{ return foo(std::move(*pb)); };
}
a();
将b
的移动副本推送到共享指针中,将其存储在std::function
中,然后在您第一次调用()
时破坏性地使用它。
1 它实现了没有它的移动(除非它使用小函数优化,我希望它使用类型的移动)。它还实现了convert-back-to-original类型,但每种类型都支持它。对于某些类型,它支持check-for-null(即显式地转换为bool),但我老实说它不确定它的确切类型。
答案 1 :(得分:1)
你可以使用指针,lambdas和std :: bind的组合来解决std::function
auto lambda = [](Bar* b){::foo(std::move(*b));};
std::function<void()> a = std::bind(lambda, &b);
a();
约束:
std::function<void()> a = [&b](){::foo(std::move(b));};
a()
C ++ 11中的一个衬里,带有lambda和按引用捕获
shared_ptr
(将评论移到我的答案中)
在您的代码编辑之后添加了约束,该约束使得函数对象应该能够比绑定到函数的变量的范围更长,我们仍然可以使用lambda完成此操作,现在我们应该捕获{{1} }使用分配和移动构造来保存Bar
。
在下面的示例中,我使用C ++ 14的通用捕获来捕获shared_ptr。 Example2将其转换为C ++ 11。
std::function<void()> a;
{
Bar b;
b.i = 10;
a = [b2 = std::make_shared<decltype(b)>(std::move(b))]()
{
// move the underlying object out from under b2
// which means b2 is in a valid but undefined state afterwards
::foo(std::move(*b2));
};
}