Reputation: 314
I'm pretty new with c++ and am currently writing an inversion of control container for my first project, expanding on this blog post by adding registering on base classes and forwarding additional arguments to the constructor.
It works pretty good for now but when I instantiate a lambda multiple times the captured values seem to get overridden.
Example:
struct A{
short a;
explicit A(short a_) : a(a_) {}
};
struct IC{
virtual unsigned C() = 0;
};
struct CImpl : public IC{
explicit CImpl(unsigned c_) : IC(), c(c_) {}
unsigned C() override{return c;}
private:
unsigned c;
};
template<class T, typename...TArgs>
std::function<T*()> AsMinimalAsItGets(TArgs&&...args)
{
return [&args...]() mutable -> T*
{
return new T(std::forward<TArgs>(args)...);
};
}
auto aFactory = AsMinimalAsItGets<A>(3);
auto cFactory = AsMinimalAsItGets<CImpl>(5);
auto aInst = aFactory();//aInst->a should be 3 but is 5
auto cInst = cFactory();//cInst->C() is 5
A gets instantiated with 5 instead of 3.
I tried this as solution, but it didn't fix the problem.
So how do I correctly capture the variables when instantiating the lambda? I need to capture in a way that enables me to use perfect forwarding in the lambda
Upvotes: 4
Views: 1448
Reputation: 38287
Don't try to avoid a copy when you actually need one. In your case, you try to preserve the value categories of the arguments by std::forward
them. But when you return a factory function std::function<T*()>
, this closure must own the data it uses to perform the delayed construction. Otherwise, you end up with dangling references, as the arguments passed to AsMinimalAsItGets
only outlive the scope of the function call.
The fix is easy:
template<class T, typename...TArgs>
std::function<T*()> AsMinimalAsItGets(TArgs&&...args)
{
return [args...]() mutable -> T*
// ^^^^^^^ (1) Copy the arguments into the closure
{
return new T(args...);
// ^^^^^^^ (2) Pass them as is to the ctor
};
}
Note that as @HolyBlackCat pointed out, this does not perfectly forward the arguments into the lambda capture. As shown in this answer, in C++20, you can
return [...args = std::forward<TArgs>(args)]() mutable -> T*
{
return new T(args...);
};
while in C++17, you need this workaround:
return [args = std::make_tuple(std::forward<TArgs>(args)...)]() mutable -> T*
{
return std::apply([](auto&&... args){ return new T(args...); },
std::move(args));
};
Upvotes: 3