Implementing virtual functions' overriding mechanism with templates

Viewed 54

I recently had a thought of implementing virtual functions without virtual tables or storing a pointer with CRTP (though using static_cast<CRTP&>(*this) instead.

The initial set up is rather cumbersome compared to conventional virtual functions.

So the code is:

namespace detail
{
    template<typename T, typename = void>
    struct virtual_set_up
    {
        void operator()(T &) {}
    };

    template<typename T>
    struct virtual_set_up<T, std::void_t<decltype(std::declval<T>().set_up())>>
    {
        void operator()(T &t) { t.set_up(); }
    };
}

template<typename CRTP>
class base
{
public:
    base() {}

    void set_up() { detail::virtual_set_up<CRTP>()(static_cast<CRTP &>(*this)); }

protected:
    ~base() = default;
};

class settable : public base<settable>
{
public:
    void set_up() { std::cout << "settable: set_up overridden" << std::endl; }
};

class dummy : public base<dummy>
{
public:
};

int main(int, char **)
{
    settable s;
    dummy d;

    base<settable>& baseS = s;
    base<dummy>& baseD = d;
    baseS.set_up();
    baseD.set_up();

    return 0;
}

However there is a problem: virtual_set_up<dummy> resolves to the specialization of T with declared T::set_up causing a SEGFAULT upon execution. It happens because dummy is publicly inheriting from base, which does have a set_up method.

Given that the previous problem is solvable, does this add any efficiency over having a conventional virtual function?

1 Answers

To solve your infinite recursion, you might still compare that "&dummy::setup != &base<dummy>::setup":

namespace detail
{
    template <typename B, typename T, typename = void>
    struct virtual_set_up
    {
        void operator()(T&) {}
    };

    template <typename B, typename T>
    struct virtual_set_up<B, T,
               std::enable_if_t<!std::is_same_v<decltype(&B::set_up),
                                                decltype(&T::set_up)>>>
    {
        void operator()(T& t) { t.set_up(); }
    };
}

template <typename CRTP>
class base
{
public:
    base() {}

    void set_up() { detail::virtual_set_up<base, CRTP>()(static_cast<CRTP &>(*this)); }

protected:
    ~base() = default;
};

Demo

But simpler would be to rename/split the one in base<CRTP>

template <typename CRTP>
class base
{
public:
    base() {}

    void set_up() { static_cast<CRTP &>(*this).set_up_v(); }

    void set_up_v() { std::cout << "base\n"; }

protected:
    ~base() = default;
};

class settable : public base<settable>
{
public:
    void set_up_v() { std::cout << "settable: set_up overridden" << std::endl; }
};

Demo

Does this add any efficiency over having a conventional virtual function?

All code there are resolve at compilation, there are no dynamic dispatch, so no overhead of virtual dispatch...

But you have nothing which is polymorphic neither here: base<dummy> and base<settable> are unrelated classes (you cannot have std::vector<base> to store then together). So comparison is unfair.

For case where all types are known at compile-time, compilers might use devirtualization optimization and remove the overhead of virtual call too.

Related