Looking at some static-allocation replacements for std::function
, ones that don't involve any std
libraries.
Soon to realize that some only support stateless lambdas (any reason for that? ) .
E.g., vl seems to work fine with [&]
, while etl does not - I get garbage values.
My test code:
template<typename Func, typename... Args>
class Callback
{
public:
Func dFunc;
etl::delegate<void(Func v, int& ret )> dLambda;
vl::Func<void(Func func, int& ret)> dLambda1;
Callback(Func func, Args&...args) :
dFunc(func),
dLambda([&args...](Func v, int& ret) { ret = v(args...); }),
dLambda1([&args...](Func v, int& ret) { ret = v(args...); })
{}
void run(int& ret)
{
dLambda(dFunc, ret );
//auto test = dLambda;
//CHECK_EQUAL(true, is_stateless<decltype(test)>::value);
}
void run1(int & ret)
{
dLambda1(dFunc, ret);
//auto test1 = dLambda1;
//CHECK_EQUAL(true, is_stateless<decltype(test1)>::value);
}
};
I tested them both for is_stateless
stateless test , both said they are not! (??)
Furthermore the test itself 'fixed' a non working replacement (??)
I would like to know what is added to the ones that do support [&]
? What should I look for?
CodePudding user response:
ETL documents that etl::delegate
doesn't own the lambda at all, see https://www.etlcpp.com/delegate.html. It only stores a pointer to the passed object. It doesn't store the lambda at all. See also the code at https://github.com/ETLCPP/etl/blob/master/include/etl/private/delegate_cpp11.h#L117.
Contrary to what the linked documentation seems to imply, it is always undefined behavior to pass the constructor of etl::delegate
a lambda expression directly as argument, no matter whether it is stateless or not. Because etl::delegate
stores a pointer to the passed object, when operator()
is called it will try to call a non-static member function on an out-of-lifetime object.
The undefined behavior is just less likely to cause unintended behavior of the compiled program if the lambda is stateless and so the compiled member function called on the invalid pointer doesn't actually have to access/dereference the pointer.
You must store the lambda somewhere outside the etl::delegate
.
I am not sure why the author didn't add a lambda-to-function-pointer conversion for non-capturing lambdas and/or disallowed rvalue arguments to the constructor. The way it is written now it is extremely easy to misuse.
vl::Func
seems to implement a pattern similar to std::function
and does own the passed callable. That also means it uses new
to store the callable in dynamic memory, but no matching delete
as far as I can tell.
Therefore it is not a static-allocation replacement. It does use dynamic allocation.
(Sorry for my previous assertion that it is leaking the object. I completely misread the code. I don't see any defect in the implementation anymore as far as the superficial look I had at the code.)
It is not possible to implement what std::function
does in generality without dynamic allocation. A static allocation replacement for std::function
which also owns the callable must have some parameter limiting the maximum size of a callable object it can store or something similar because the callable would need to be embedded directly into the storage of the std::function
-replacement object.
From the examples above you can see that problem. Both of them don't have such a parameter. So one of the replacements isn't owning and the other isn't statically allocating, they can't be doing both.
To test whether the lambda is stateless you need to apply is_stateless
to the type of the lambda expression, e.g.
auto lambda = [&args...](Func v, int& ret) { ret = v(args...); };
CHECK_EQUAL(true, is_stateless<decltype(lambda)>::value);
You are applying is_stateless
to a type that is not a lambda at all.