Reputation: 33579
I'm sure there's a name for what I'm looking for, I just don't know it (and if I did, I'd probably find the answer already). Basically, I want to implement my own lightweight version of std::function
for sports. I want to initialize it with a lambda, and later invoke it. I can wrap the lambda with my template wrapper class, nothing to it:
struct CInvokableAbstract {
virtual ~CInvokableAbstract() = default;
};
template <class InvokableObject>
struct CInvokableBasic : public CInvokableAbstract
{
CInvokableBasic(InvokableObject&& target) : _invokable(std::move(target)) {}
template <typename... Args>
typename std::result_of<decltype(&InvokableObject::operator())(Args...)>::type operator()(Args... args) const
{
return _invokable(std::forward<Args>(args)...);
}
private:
InvokableObject _invokable;
};
Now I can make my class that's semantically similar to std::function
, but how can I store the exact type of the lambda in order to convert the type-erased object back to its original concrete type?
struct CInvokableDeferred
{
template <class InvokableObject>
CInvokableDeferred(InvokableObject&& target) noexcept : _invokable(std::make_unique<CInvokableBasic<InvokableObject>>(std::move(target))) {}
template <typename... Args>
void operator()(Args... args) const
{
// How can I know the original concrete type to cast this to?
static_cast<???>(_invokable.get())->(std::forward<Args>(args)...);
}
private:
std::unique_ptr<CInvokableAbstract> _invokable;
};
I can't think of any template trickery that could do that, yet we know it's possible (unless std::function
uses some compiler built-ins, or otherwise is implemented internally in the compiler rather than being normal C++ code).
Note that I'm using a compiler that doesn't have full C++17 support, so I can't use e. g. auto
-deduced return type.
Upvotes: 3
Views: 1006
Reputation: 6144
You need to rewrite your base class as follows:
template <typename Ret, typename... Args>
class CInvokableAbstract {
virtual ~CInvokableAbstract() = default;
virtual Ret operator()(Args... args) = 0;
};
This will make your base class dependent on the signature (which it has to be in order to be usable) and provide the actual interface for the invocable object.
Note that this part of code actually has nothing to do with type-erase, it's just plain old dynamic polymorphism. It's the combination of static (CInvokableBasic
template) and dynamic (CInvokableAbstract
interface) polymorphisms that make type-erasure possible.
Upvotes: 4