Как в классах C++ подставлять различные реализации методов на этапе компиляции?

Хотелось бы понять, как можно выбирать реализацию некоторого С++ метода, наподобии такого подхода:

#include <iostream>

class A {
public:
    void foo()
    {
        std::cout << "foo A" << std::endl;
    }   
};

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

template <typename T>
class C : public T {

};

int main () {
    C<A> ca;
    C<B> cb;

    // Можно заметить, что вызов функций происходит напямую - без indirection call оверхеда.
    // Никаких указателей на функции, значения которых будут разыменовываться в райнтайме

    ca.foo();   // call   4011f0 <A::foo()>
    cb.foo();   // call   40121c <B::foo()>

    return 0;
}

Вопрос в следующем - может быть есть какой-нибудь способ на месте подставлять конкретные адреса вызова функций, но не используя шаблонных конструкций и/или наследования, при этом имея возмонжость легко переключаться между различными реализациями?

Например, вынести тип, из которого мы хотим извлекать адрес функции, используя псевдоним типа, что-то вроде:

#include <iostream>

class A {
public:
    void foo()
    {
        std::cout << "foo A" << std::endl;
    }   
};

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

class C {
public:
    using T = B;

    /* Тут и происходит магия, которая выполняет подстановку адреса функции */
};

int main () {
    C cb;
    cb.foo();   // call   40121c <B::foo()>

    return 0;
}

Спасибо.


Ответы (1 шт):

Автор решения: Mister_Jesus

Достаточно просто вызывать нужный метод из базового класса. Плюс проверка на то, что C наследуется от нужного типа. Но заметьте, что C<A,B> != C<B,A> (Да и вообще все варианты C<...> не будут одним и тем же классом), так что при таком стиле рано или поздно появятся сложности.

#include <utility>
#include <iostream>

struct A {
    void foo() {
        std::cout << "foo A" << std::endl;
    }
};

struct B {
    void foo() {
        std::cout << "foo B" << std::endl;
    }
};

template <typename ... Args>
struct C : public Args ... {

    template <typename T> requires std::is_base_of_v<T, C>
    void foo() {
        T::foo();
    }
};

int main() {
    C<A, B> l;
    l.foo<A>();
    l.foo<B>();
}
→ Ссылка