Violet Giraffe
Violet Giraffe

Reputation: 33579

How to store and restore the exact type of a type-erased object?

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

Answers (1)

r3mus n0x
r3mus n0x

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

Related Questions