Search code examples
c++c++17luabridge

Downcasting to furthest subclass when calling templated function


I use LuaBridge to import a large framework of classes into a Lua-accessible framework. LuaBridge uses complex template functions that maintain a list of linkages back to methods and properties of each class. The Lua language itself is loosely typed, and it does not check to see if a method or property exists until you call it. My framework implements a ClassName method at every level that allows the Lua programs to know which class it is dealing with.

That's just background for my program. This is a C++ question. I would like to call a function that, in its broadest abstraction, looks something like this:

template <typename T>
do_something_in_lua(T * object); // LuaBridge will create a pointer to a T instance in Lua

and call it like this:

class foobase
{
public:
    void notify_lua()
    { do_something_in_lua(this); }
};

class foo : public foobase
{
public:
    int some_method();
};

foo x;
x.notify_lua();

My question is, is there a simple way for do_something_in_lua to use the maximally downcasted version of T? Either when I call it or in the templated function? Using dynamic_cast is difficult because I would have to maintain an explicit list of every possible subclass to find the right one. Even if it is adding a templated virtual function in foobase that returns the desired type of this, I would be interested in suggestions for an elegant solution.

I guess a broader version of my question would be, does modern C++ provide any tools for downcasting in template programming (e.g., in type_traits or the like) that I should be investigating?


Solution

  • Thanks to several helpful comments, the solution turns out to be a hybrid of CRTP and Double Dispatch. Here is a version of it based on my example above. I like the fact that it

    • requires no pure virtual functions
    • does not require templatizing the base class (for reasons specific to my code base)

    If I ever need to add a new subclass, I just need to add it to the list in the std::variant, and better yet, the compiler will complain until I do so.

    template <typename T>
    void do_something_in_lua(T * object);
    
    // every subclass of foobase must be listed here.
    class foosub1;
    class foosub2;
    using foobase_typed_ptr = std::variant<foosub1*, foosub2*>;
    
    class foobase
    {
        foobase_typed_ptr _typedptr;
       
    public:
        foobase(const foobase_typed_ptr &ptr) : _typedptr(ptr) {}
        void notify_lua()
        {
            std::visit([](auto const &ptr)
                { do_something_in_lua(ptr); },
                _typedptr);
        }
    };
    
    class foosub1 : public foobase
    {
    public:
        foosub1() : foobase(this) {}
        int some_method1();
    };
    
    class foosub2 : public foobase
    {
    public:
        foosub2() : foobase(this) {}
        int some_method2();
    };
    
    // and to call it in code:
    
    foosub2 x;
    x.notify_lua(); // now Lua gets called with a foosub2* instead of a foobase*.