Как реализовать статическую функцию виртуального члена

TL/DR: есть ли (более удобный) способ реализовать такую ​​функциональность?

Я должен вызвать один и тот же статический код как для типа класса, так и для экземпляра класса, представленного ссылкой на базу:

int main()
{
    // Invokes on class
    bar<C1>(); // invokes C1::foo()
    bar<C2>(); // invokes C2::foo()

    // Invokes on instance
    bar(C1()); // invokes C1::foo()
    bar(C2()); // invokes C2::foo()
}

Но проблема в том, что я не могу реализовать это без некоторого дублирования кода. Для каждого производного класса я должен написать как статические, так и виртуальные методы. Статический - потому что я не могу вызвать виртуальный метод в классе и виртуальный, который вызывает статический - потому что нет никакого способа отличить поведение объекта, кроме как с помощью виртуальных методов:

template<typename T>
void bar()
{
    T::foo();
}

void bar(A const &r)
{
    r.foo();
}

Таким образом, мой обходной путь для решения проблемы дублирования кода:

#include <iostream>

class A
{
public:
    virtual void foo() const = 0;
};

template<typename derived_T>
class B: public A
{
public:
    virtual void foo() const
    {
        derived_T::foo();
    }
};

class C1 : public B<C1>
{
public:
    static void foo()
    {
        std::cout << "C1::foo()" << std::endl;
    }
};

class C2 : public B<C2>
{
public:
    static void foo()
    {
        std::cout << "C2::foo()" << std::endl;
    }
};

Этот подход работает отлично, но у него есть как минимум два неудобства.

Во-первых, мне пришлось представить вспомогательный шаблон класса B с реализованным виртуальным методом.

Во-вторых, каждая цепочка наследования (от B до конечного класса) должна состоять из шаблонов, что делает невозможным использование какого-либо промежуточного класса в качестве указателя / ссылочного типа. например. A <- B <- T1 <- T2 <- C, T1 и T2 должны быть шаблонными классами для обеспечения C:: foo ().

2 ответа

Решение

Одним из возможных решений является CRTP с инъекцией базового класса.

template <typename T, typename... Bases> 
struct CRTPFooInjector : Bases... 
{
    virtual void vfoo() { T::foo(); }
};    

Это ваш шаблон инжектора. Он реализует только виртуальную версию foo, ничего больше.

struct Base: CRTPFooInjector<Base>
{
    static int foo() { std::cout << "Base::foo()" << std::endl; }
};


struct Der1 : CRTPFooInjector<Der1, Base>
{
    static int foo() { std::cout << "Der1::foo()" << std::endl; }
};


struct Der2 : CRTPFooInjector<Der2, Base>
{
    static int foo() { std::cout << "Der2::foo()" << std::endl; }
};


struct Der12 : CRTPFooInjector<Der12, Der1, Der2>
{
    static int foo() { std::cout << "Der12::foo()" << std::endl; }
};

Теперь вместо вашей обычной иерархии Base <- Der1 <- Der12 у вас немного другой Injector <- Base <- Injector <- Der1 <- Injector <- Der12, но это должно быть прозрачным для большинства пользователей.

Если вам нужно смешать несколько виртуальных базовых классов:

template <typename T>
struct Virtual : virtual T
{
};

struct Der1 : CRTPFooInjector<Der1, Virtual<Base>> ...

Если у вас есть несколько статических методов, чтобы ваши производные классы были исправлены, вы можете использовать шаблон посетителя:

class IAVisitor;
struct A
{
    virtual ~A() = default;
    virtual void accept(IAVisitor& visitor) = 0;
};
struct C1; struct C2; /*..*/; struct Cn;
struct IAVisitor
{
    virtual ~IAVisitor() = default;
    virtual visit(C1&) = 0;
    virtual visit(C2&) = 0;
    //...
    virtual visit(Cn&) = 0;
};

struct C1 : A
{
    void accept(IAVisitor& visitor) override {visitor.visit(*this);};
    static void foo();
};
// Similar code for C2, Cn

template <typename F>
struct AVisitor_F : IAVisitor
{
    AVisitor_F(F f) : f(f) {}
    void visit(C1& a) override { f(a); }
    void visit(C2& a) override { f(a); }
    //...
    void visit(Cn& a) override { f(a); }

    F f;
};

И с

struct FooCaller
{
    template<typename T>
    void operator () (const T&) const
    {
        T::foo();
    }
};

И наконец:

AVisitor_F<FooCaller> fooVisitor;
a.accept(fooVisitor);
Другие вопросы по тегам