Ответ 1
std::function
не может принимать вызовы только для перемещения. Он стирает переданный тип, чтобы вызвать (с подписью), уничтожить и скопировать. 1
Запись только для перемещения std::function
- это только немного работы. Вот удар по нему в другом контексте. живой пример.
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)...);
};
}
который обертывает некоторый вызываемый объект в общий указатель, помещает это в лямбда идеальной пересылкой лямбда.
Это иллюстрирует проблему - вызов не работает! Все вышесказанное имеет const
invokation.
То, что вы хотите, - это задача, которую вы можете вызвать только один раз.
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() )
{}
};
Обратите внимание, что вы можете вызывать только ()
в контексте rvalue с помощью вышеприведенного task_once
. Это связано с тем, что ()
является деструктивным, как и должно быть в вашем случае.
К сожалению, вышеизложенное полагается на С++ 14. И мне не нравится писать код С++ 11 в наши дни. Итак, вот более простое решение С++ 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 Он реализует перемещение без него (если только он не использует оптимизацию небольших функций, где я надеюсь, что он использует перемещение типа). Он также реализует тип конвертирования назад, но каждый тип поддерживает это. Для некоторых типов он поддерживает check-for-null (т.е. Cast to bool явно), но я честно не уверен в том, какие именно типы он делает.