Không có sự giết người nào như quá mức cần thiết.
Bước 1: viết một SFINAE thân thiện std::result_of
và một chức năng để giúp kêu gọi thông qua 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>{};
Bây giờ chúng ta có invoke_result_t<A(B,C)>
mà là một SFINAE thân thiện result_of_t<A(B,C)>
và can_invoke<A(B,C)>
mà chỉ làm việc kiểm tra.
Tiếp theo, hãy viết một move_only_function
, một phiên bản di chuyển chỉ của 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
};
không dự thi, chỉ phun mã. can_invoke
cung cấp cho hàm tạo SFINAE cơ bản - bạn có thể thêm "kiểu trả về chuyển đổi đúng" và "loại trả về void có nghĩa là chúng tôi bỏ qua trả lại" nếu bạn muốn.
Bây giờ chúng tôi sẽ làm lại mã của bạn. Thứ nhất, nhiệm vụ của bạn là chức năng di chuyển chỉ, không phải chức năng:
std::vector<move_only_function<X>> mTasks;
Tiếp theo, chúng tôi lưu trữ các tính R
loại một lần, và sử dụng nó một lần nữa:
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;
}
chúng ta nhét các đối số vào một tuple, chuyển tuple đó vào lambda và gọi tuple theo kiểu "chỉ làm điều này một lần" trong lambda. Vì chúng ta sẽ chỉ gọi hàm một lần, chúng ta tối ưu hóa lambda cho trường hợp đó.
A packaged_task<R()>
tương thích với move_only_function<R()>
không giống như std::function<R()>
, vì vậy chúng tôi chỉ có thể di chuyển nó vào vectơ của chúng tôi. Các std::future
chúng tôi nhận được từ nó sẽ làm việc tốt mặc dù chúng tôi đã nhận nó trước khi move
.
Điều này sẽ giảm chi phí của bạn một chút. Tất nhiên, có rất nhiều boilerplate.
Tôi chưa biên soạn bất kỳ mã nào ở trên, tôi đã giải thích nó, vì vậy tỷ lệ cược tất cả các biên dịch đều thấp. Nhưng các lỗi chủ yếu là tpyos.
Ngẫu nhiên, tôi quyết định cung cấp cho move_only_function
4 quá tải khác nhau ()
(rvalue/lvalue và const/not). Tôi có thể đã thêm dễ bay hơi, nhưng điều đó có vẻ thiếu thận trọng. Mà tăng boilerplate, thừa nhận.
Ngoài ra, move_only_function
của tôi thiếu thao tác "lấy tại công cụ được lưu trữ cơ bản" mà std::function
có. Hãy gõ xóa rằng nếu bạn thích.Và nó xử lý (R(*)(Args...))0
như thể nó là một con trỏ hàm thực (Tôi trở true
khi đúc để bool
, không giống như null: loại chô bôi của convert-to- bool
có thể đáng giá cho việc thực hiện công nghiệp chất lượng hơn
Tôi viết lại std::function
. vì std
thiếu std::move_only_function
, và khái niệm nói chung là một trong những hữu ích (được minh chứng bởi packaged_task
). giải pháp của bạn làm cho di chuyển callable của bạn bằng cách gói nó với một std::shared_ptr
.
Nếu bạn không thích những soạn sẵn ở trên, xem xét viết make_copyable(F&&)
, có đối tượng hàm F
và kết thúc tốt đẹp bằng cách sử dụng kỹ thuật shared_ptr
của bạn để làm cho nó có thể sao chép được. Bạn thậm chí có thể thêm SFINAE để tránh làm điều đó nếu nó đã được sao chép (và gọi nó là ensure_copyable
).
Sau đó, mã ban đầu của bạn sẽ sạch hơn, vì bạn chỉ cần tạo packaged_task
có thể sao chép, sau đó lưu trữ mã đó.
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;
}
yêu cầu này vẫn yêu cầu invoke_tuple
boilerplate ở trên, chủ yếu là vì tôi không thích bind
.
Tại sao bạn không đặt tác vụ đóng gói trực tiếp vào vectơ? –
@KerrekSB Vì 'packaged_task' không thể sao chép được và' std :: function' chỉ chấp nhận những thứ có thể sao chép được. – David
Tôi có nghĩa là 'std :: vector>'. –