-
Notifications
You must be signed in to change notification settings - Fork 194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
scheduler: Support moved tast captures / arguments #216
Conversation
Hello, is there any update for this? |
Replace the internal use of `std::function` with `std::packaged_task`. `std::function` requires that the wrapped function is CopyConstructable, where as a `std::packaged_task` does not. This allows the tasks to hold `std::move`'d values. This is an API / ABI breaking change, but I believe few people would be copying `marl::Task`s.
6058c63
to
a2ca890
Compare
I have another solution. #include <iostream>
#include <type_traits>
#include <functional>
#include <memory>
#include <utility>
#include <cassert>
template <typename Callable>
void task_proxy(void* vp) {
auto invoker_ptr(static_cast<Callable*>(vp));
if (invoker_ptr) {
(*invoker_ptr)();
}
}
class task {
public:
template <class Fn, typename = std::enable_if_t<std::is_invocable_v<Fn&&>>>
task(Fn&& func) {
using Wrapper = typename std::decay_t<Fn>;
auto __vp = new Wrapper(std::forward<Fn>(func));
proxy = &task_proxy<Wrapper>;
vp.reset(__vp);
}
void operator()() const { proxy(vp.get()); }
explicit operator bool() { return proxy.operator bool(); }
private:
std::function<void(void*)> proxy;
std::shared_ptr<void> vp;
};
void print() {
std::cout << "Hello World\n";
}
int main() {
const char* s = "Hello World";
auto uptr = std::make_unique<int>(2);
// Test lambda
task t([u = std::move(uptr), s]() {
assert(*u = 2);
std::cout << s << std::endl;
});
t();
// Test function name
task t2(print);
t2();
// Test function pointer
task t3(&print);
t3();
// Test member method
struct A {
void operator()() {}
};
A a;
task t4(a);
t4();
// Test bind
task t5(std::bind(&A::operator(), a));
t5();
} |
MSVC doesn't like this change:
I haven't had time to see if I can find a work around for this.
Thank you for the suggestion. The heap allocation is costly to do for all tasks. I guess there's nothing stopping a user from using this work-around as something they'd pass to |
Implement a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks okay to me. Not super familiar with std::packaged_task
, but reading about it, it looks like there would be some size and runtime overhead as compared to std::function
because it stores a std::future
of the callable's return value; however, in this case, the return type is void
so this probably doesn't make much difference.
This is a solution referring to struct Task {
// template <class Fn, typename = std::enable_if_t<std::is_invocable_v<Fn>>>
template <class Fn>
Task(Fn&& func) {
using Wrapper = typename std::decay<Fn>::type;
init<Wrapper>(std::forward<Fn>(func));
invoker = &Invoke<Wrapper>;
}
~Task() {
if (manager) {
manager(storage, nullptr);
}
}
void operator()() const {
invoker(&storage);
}
struct Storage {
void* addr() noexcept { return &bytes[0]; }
const void* addr() const noexcept { return &bytes[0]; }
struct Delegate { void (Storage::*pfm)(); Storage* obj; };
union {
void* ptr;
alignas(Delegate) alignas(void(*)())
unsigned char bytes[sizeof(Delegate)];
};
};
template<typename T>
static constexpr bool stored_locally
= sizeof(T) <= sizeof(Storage) && alignof(T) <= alignof(Storage);
// && std::is_nothrow_move_constructible_v<T>;
template <class T>
static void Manage(Storage& target, Storage* src) noexcept {
if (stored_locally<T>) {
if (src) {
T* rval = static_cast<T*>(src->addr());
::new (target.addr()) T(std::move(*rval));
rval->~T();
} else {
static_cast<T*>(target.addr())->~T();
}
} else {
if (src) {
target.ptr = src->ptr;
} else {
delete static_cast<T*>(target.ptr);
}
}
}
template <class T>
static void Invoke(Storage* vp) {
if (stored_locally<T>) {
auto invoker_ptr(static_cast<T*>(vp->addr()));
if (invoker_ptr) {
(*invoker_ptr)();
}
} else {
auto invoker_ptr(static_cast<T*>(vp->ptr));
if (invoker_ptr) {
(*invoker_ptr)();
}
}
}
template <class T, class... Args>
void init(Args&&... args) {
if (stored_locally<T>) {
new (storage.addr()) T(std::forward<Args>(args)...);
} else {
storage.ptr = new T(std::forward<Args>(args)...);
}
manager = &Manage<T>;
}
using Invoker = void(*)(Storage*);
using Manager = void(*)(Storage&, Storage*);
Invoker invoker = nullptr;
mutable Storage storage;
Manager manager = nullptr;
}; |
The master branch did not see this commit yet. lambda still not support unique_ptr. Why is this request not submitted? |
Because the PR does not compile with MSVC, and suggested alternatives will add unacceptable overhead to each scheduled task. If someone can create a PR that adds move-support with no additional overhead, then I'd happily review it. Closing this PR. |
Replace the internal use of
std::function
withstd::packaged_task
.std::function
requires that the wrapped function is CopyConstructable, where as astd::packaged_task
does not.This allows the tasks to hold
std::move
'd values.This is an API / ABI breaking change, but I believe few people would be copying
marl::Task
s.Fixes: #211