Non c'è uccisione come eccessivo.
Fase 1: scrivere uno SFINAE accogliente std::result_of
e una funzione per aiutare chiamare via tuple:
namespace details {
template<size_t...Is, class F, class... Args>
auto invoke_tuple(std::index_sequence<Is...>, F&& f, std::tuple<Args>&& args)
{
return std::forward<F>(f)(std::get<Is>(std::move(args)));
}
// SFINAE friendly result_of:
template<class Invocation, class=void>
struct invoke_result {};
template<class T, class...Args>
struct invoke_result<T(Args...), decltype(void(std::declval<T>()(std::declval<Args>()...))) > {
using type = decltype(std::declval<T>()(std::declval<Args>()...));
};
template<class Invocation, class=void>
struct can_invoke:std::false_type{};
template<class Invocation>
struct can_invoke<Invocation, decltype(void(std::declval<
typename invoke_result<Inocation>::type
>()))>:std::true_type{};
}
template<class F, class... Args>
auto invoke_tuple(F&& f, std::tuple<Args>&& args)
{
return details::invoke_tuple(std::index_sequence_for<Args...>{}, std::forward<F>(f), std::move(args));
}
// SFINAE friendly result_of:
template<class Invocation>
struct invoke_result:details::invoke_result<Invocation>{};
template<class Invocation>
using invoke_result_t = typename invoke_result<Invocation>::type;
template<class Invocation>
struct can_invoke:details::can_invoke<Invocation>{};
Ora abbiamo invoke_result_t<A(B,C)>
che è uno SFINAE accogliente result_of_t<A(B,C)>
e can_invoke<A(B,C)>
che fa proprio l'assegno.
Avanti, scrivere un move_only_function
, una versione mossa solo di std::function
:
namespace details {
template<class Sig>
struct mof_internal;
template<class R, class...Args>
struct mof_internal {
virtual ~mof_internal() {};
// 4 overloads, because I'm insane:
virtual R invoke(Args&&... args) const& = 0;
virtual R invoke(Args&&... args) & = 0;
virtual R invoke(Args&&... args) const&& = 0;
virtual R invoke(Args&&... args) && = 0;
};
template<class F, class Sig>
struct mof_pimpl;
template<class R, class...Args, class F>
struct mof_pimpl<F, R(Args...)>:mof_internal<R(Args...)> {
F f;
virtual R invoke(Args&&... args) const& override { return f(std::forward<Args>(args)...); }
virtual R invoke(Args&&... args) & override { return f(std::forward<Args>(args)...); }
virtual R invoke(Args&&... args) const&& override { return std::move(f)(std::forward<Args>(args)...); }
virtual R invoke(Args&&... args) && override { return std::move(f)(std::forward<Args>(args)...); }
};
}
template<class R, class...Args>
struct move_only_function<R(Args)> {
move_only_function(move_only_function const&)=delete;
move_only_function(move_only_function &&)=default;
move_only_function(std::nullptr_t):move_only_function() {}
move_only_function() = default;
explicit operator bool() const { return pImpl; }
bool operator!() const { return !*this; }
R operator()(Args...args) & { return pImpl().invoke(std::forward<Args>(args)...); }
R operator()(Args...args)const& { return pImpl().invoke(std::forward<Args>(args)...); }
R operator()(Args...args) &&{ return std::move(*this).pImpl().invoke(std::forward<Args>(args)...); }
R operator()(Args...args)const&&{ return std::move(*this).pImpl().invoke(std::forward<Args>(args)...); }
template<class F,class=std::enable_if_t<can_invoke<decay_t<F>(Args...)>>
move_only_function(F&& f):
m_pImpl(std::make_unique<details::mof_pimpl<std::decay_t<F>, R(Args...)>>(std::forward<F>(f)))
{}
private:
using internal = details::mof_internal<R(Args...)>;
std::unique_ptr<internal> m_pImpl;
// rvalue helpers:
internal & pImpl() & { return *m_pImpl.get(); }
internal const& pImpl() const& { return *m_pImpl.get(); }
internal && pImpl() && { return std::move(*m_pImpl.get()); }
internal const&& pImpl() const&& { return std::move(*m_pImpl.get()); } // mostly useless
};
non testato, solo vomitato il codice. Lo standard can_invoke
fornisce il costruttore SFINAE di base - è possibile aggiungere "restituisce il tipo convertito correttamente" e "restituire il tipo restituito vuol dire ignorare il reso" se lo si desidera.
Ora rielaboriamo il codice. In primo luogo, il vostro compito sono funzioni Move-solo, non funzioni:
std::vector<move_only_function<X>> mTasks;
Successivamente, memorizzare il calcolo R
tipo di una volta, e riutilizzarlo:
template<class F, class... Args, class R=std::result_of_t<std::decay<F>_&&(std::decay_t<Args>&&...)>>
std::future<R>
push(F&& f, Args&&... args)
{
auto tuple_args=std::make_tuple(std::forward<Args>(args)...)];
// lambda will only be called once:
std::packaged_task<R()> task([f=std::forward<F>(f),args=std::move(tuple_args)]
return invoke_tuple(std::move(f), std::move(args));
});
auto future = func.get_future();
// for some reason I get a compilation error in clang if I get rid of the `=, ` in this capture:
mTasks.emplace_back(std::move(task));
return future;
}
abbiamo roba gli argomenti in una tupla, passa quella tupla in una lambda e invoca la tupla in un modo "solo fallo una volta" all'interno della lambda. Poiché invocheremo la funzione solo una volta, ottimizzeremo la lambda per quel caso.
Un packaged_task<R()>
è compatibile con un move_only_function<R()>
a differenza di un std::function<R()>
, quindi possiamo semplicemente spostarlo nel nostro vettore. Il std::future
che otteniamo dovrebbe funzionare bene anche se lo abbiamo ottenuto prima dello move
.
Questo dovrebbe ridurre un po 'il sovraccarico. Certo, c'è un sacco di piatti.
Non ho compilato nessuno dei codici precedenti, l'ho appena estratto, quindi le probabilità che tutto sia compilato sono bassi. Ma gli errori dovrebbero essere principalmente tpyos.
A caso, ho deciso di assegnare a move_only_function
4 diversi sovraccarichi ()
(valore/valore lvalue e const/non). Avrei potuto aggiungere volatile, ma sembra spericolato. Quale aumento caldaia, certamente.
Anche il mio move_only_function
non ha l'operazione "get at the stored stuff below" che ha std::function
. Sentiti libero di digitare cancella se ti piace.E si tratta di (R(*)(Args...))0
come se fosse un puntatore a funzione reale (ho ritorno true
quando il cast a bool
, non come nulla: cancellazione del tipo di convert-to-bool
potrebbe essere utile per una realizzazione di qualità più industriale
ho riscritto std::function
. perché std
manca un std::move_only_function
, e il concetto, in generale, è un utile uno (come evidenziato da packaged_task
). la soluzione rende il vostro mobile callable avvolgendolo con un std::shared_ptr
.
Se non ti piace il boilerplate sopra, si consideri scrivendo make_copyable(F&&)
, che accetta una funzione oggetto F
e lo avvolge usando la tua tecnica shared_ptr
per renderlo riproducibile. È anche possibile aggiungere SFINAE per evitare di farlo se è già copiato (e chiamarlo ensure_copyable
).
Quindi il codice originale sarebbe più pulito, come si farebbe semplicemente copiare packaged_task
, quindi memorizzarlo.
template<class F>
auto make_function_copyable(F&& f) {
auto sp = std::make_shared<std::decay_t<F>>(std::forward<F>(f));
return [sp](auto&&...args){return (*sp)(std::forward<decltype(args)>(args)...); }
}
template<class F, class... Args, class R=std::result_of_t<std::decay<F>_&&(std::decay_t<Args>&&...)>>
std::future<R>
push(F&& f, Args&&... args)
{
auto tuple_args=std::make_tuple(std::forward<Args>(args)...)];
// lambda will only be called once:
std::packaged_task<R()> task([f=std::forward<F>(f),args=std::move(tuple_args)]
return invoke_tuple(std::move(f), std::move(args));
});
auto future = func.get_future();
// for some reason I get a compilation error in clang if I get rid of the `=, ` in this capture:
mTasks.emplace_back(make_function_copyable(std::move(task)));
return future;
}
questo richiede ancora il invoke_tuple
boilerplate sopra, soprattutto perché non mi piace bind
.
Perché non inserire l'attività pacchettizzata direttamente nel vettore? –
@KerrekSB Perché 'packaged_task's non è copiabile e' std :: function' accetta solo cose che sono copiabili. – David
Intendevo 'std :: vector>'. –