I'd like to emplace a lambda which captured a unique-pointer into a container, but it failed to compile under GCC 7.3.0 as C++17. From the error messages, it's calling the copy constructor of unique_ptr
when constructing function.
#include <memory>
#include <functional>
#include <string>
#include <queue>
int main (){
auto ptr = std::make_unique<std::string>("123456");
auto l = [p = std::move(ptr)] () mutable {
p.reset();
};
std::queue<std::function<void()>> q;
q.emplace(std::move(l));
return 0;
}
Actually, I have many lambdas with the same parameter list and same return type, and I need to emplace them to one container. So I need a common type to wrap, like std::function<void()>
.
In addition, I don't want add something else, like using std::shared_ptr
to make it copyable, or using another lambda to wrap again.
Based on the above, I designed LambdaWrapper
to use instead of std::function
. The variable which captured won't be copied, because of the non-copyable LambdaWrapper
.
class LambdaWrapper {
public:
template <class T, typename = std::is_invocable<decltype(std::mem_fn(&T::operator()))>>
LambdaWrapper(T&& lambda) {
MemberFunction memfn;
// This forced type conversion is refers to std::_Function_base::_Base_manager::_M_init_functor
new (&memfn)(decltype(&T::operator()))(&T::operator());
// Member function pointer is 16B. The first 8B is the address of function, and the last 8B is the offset of this pointer. Because the lambda type is not inherited from other classes, the offset is 0 and it can be ignored.
new (&func_) void*(memfn.func);
new (&deleter_)(void (*)(T*))(&DeleterImpl<T>::f);
data_ = malloc(sizeof(T));
new (data_) T(std::forward<T>(lambda));
}
~LambdaWrapper() {
if (data_ != nullptr && deleter_ != nullptr) {
deleter_(data_);
} else if (data_ != nullptr || deleter_ != nullptr) {
// something wrong
}
}
void operator()() { func_(data_); }
LambdaWrapper(LambdaWrapper&& T) {
data_ = T.data_;
func_ = T.func_;
deleter_ = T.deleter_;
T.data_ = nullptr;
T.func_ = nullptr;
T.deleter_ = nullptr;
}
LambdaWrapper& operator=(LambdaWrapper&& T) {
data_ = T.data_;
func_ = T.func_;
deleter_ = T.deleter_;
T.data_ = nullptr;
T.func_ = nullptr;
T.deleter_ = nullptr;
return *this;
}
private:
LambdaWrapper(const LambdaWrapper&) = delete;
LambdaWrapper& operator=(const LambdaWrapper&) = delete;
struct MemberFunction {
void* func;
void* ptr;
};
void (*func_)(void*);
void (*deleter_)(void*);
void* data_;
template <class T>
struct DeleterImpl {
static void f(T* t) { delete t; }
};
};
Finally, is there any better solution? And is there any problem with LambdaWrapper
?
1 Answer 1
Missing includes - the code won't compile until I prepend
#include <functional>
#include <stdlib.h>
#include <type_traits>
(though I would recommend <cstdlib>
and use the proper namespace for std::malloc()
).
This comment is non-portable and potentially misleading:
// Member function pointer is 16B. The first 8B is the address of function, and the last 8B is the offset of this pointer. Because the lambda type is not inherited from other classes, the offset is 0 and it can be ignored.
Sizes of pointer types are implementation-dependent, and the structure of a member function pointer is entirely unspecified. This decomposition is entirely compiler-specific, so should be guarded to prevent hard-to-diagnose errors.
data_
is created using std::malloc()
but released using delete
. That's a definite bug, and shows up immediately when exercised under Valgrind.
The operator()
looks broken - it only allows use of a no-args function, and discards the result. I would have expected something more like
template<typename... Args>
std::invoke_result<T, Args...>
operator()(Args&&... args);
The constructors probably ought to initialise members where possible, rather than assigning. Consider writing a swap()
member function, and using that to implement the move operations.
Overall, I'm disappointed that we don't have something that's much more like std::function<>
, given that we'd like to use it in a similar way, only with move-only callables.
LambdaWrapper<>
looks suspiciously like aunique_ptr<>
. Maybe it should be calledunique_function<>
? \$\endgroup\$std::unique_function<>
was proposed, but it's not in C++20: open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0228r3.html \$\endgroup\$std::function
without the requirement to be copy-constructible or copy-assignable. Which is something I had naively assumed that the standard function would be when wrapping a move-only type. \$\endgroup\$