Статический полиморфизм для выбора метода и объекта в C++

Попытка получить метод времени компиляции и выбор объекта без базового класса и виртуальных вызовов.

Вот случай:

struct A {
    void f1()const { cout << "A::f1" << endl;}
    void f2()const { cout << "A::f2" << endl;}
};

struct B {
    void f1()const { cout << "B::f1" << endl;}
    void f2()const { cout << "B::f2" << endl;}
};

class Holder {
    A* _a = nullptr;
    B* _b = nullptr;
public:
    Holder(A* a): _a(a) {}
    Holder(B* b): _b(b) {}
    void f1()const {
        if(_a)       _a->f1();
        else if(_b)  _b->f1();
    }
    void f2()const {
        if(_a)       _a->f2();
        else if(_b)  _b->f2();
    }
};

void f(const Holder& h) {
    h.f1();
}

int main() {
    B obj;
    Holder h(&obj);
    f(h);
}

http://coliru.stacked-crooked.com/a/4b5acec6866cfd4e

Предположим, что очень мало классов, таких как A и B, но может быть много функций, таких как f1 и f2.

Держателю необходимо вызвать функцию для реального объекта, который он содержит, без полиморфизма и без необходимости наследования / общего интерфейса для A и B.

Ищете хороший способ сделать что-то вроде:

class Holder {
    A* _a = nullptr;
    B* _b = nullptr;
public:
    Holder(A* a): _a(a) {}
    Holder(B* b): _b(b) {}
    // below is pseudo code!
    void call<function>()const {
        if(_a)
            _a->function(); // function is known in compile time, sort of...
        else if(_b)
            _b->function();
    }

    void f1()const { call<f1>(); }
    void f2()const { call<f2>(); }
};

Любая идея?

  • макрос?
  • шаблон?
  • другой трюк?

2 ответа

Решение

Вы можете использовать вариант. Это уменьшает объем памяти с N до 1 указателя плюс 1 int и не требует изменения ничего, кроме Holder:

#include <boost/variant.hpp>

struct f1visitor
{
    typedef void result_type;
    template<typename T>
    void operator()(T* const t) const { t->f1(); }
};

struct f2visitor
{
    typedef void result_type;
    template<typename T>
    void operator()(T* const t) const { t->f2(); }
};

class Holder {
    boost::variant<A*, B*> _ptr;
public:
    Holder(A* a): _ptr(a) {}
    Holder(B* b): _ptr(b) {}
    void f1()const {
        boost::apply_visitor(f1visitor(), _ptr);
    }
    void f2()const {
        boost::apply_visitor(f2visitor(), _ptr);
    }
};

С достаточно новым C++ вы можете использовать std::variant вместо.

Работа над решением, предложенным Джоном Цвинком, и попытка уменьшить количество повторных обращений к boost::apply_visitor(f1visitor(), _ptr); Я перешел к этому коду:

struct A {
    void f1()const { cout << "A::f1" << endl;}
    void f2(const std::string& s)const { cout << "Hello " << s << endl;}
    int  f3(int i, int j, int k)const { return i + j + k; }
};

struct B {
    void f1()const { cout << "B::f1" << endl;}
    void f2(const std::string& s)const { cout << "Shalom " << s << endl;}
    int  f3(int i, int j, int k)const { return i * j * k; }
};

class Holder {
    boost::variant<A*, B*> _ptr;
public:
    template<typename T> Holder(T* ptr): _ptr(ptr) {}
    CREATE_DELEGATE_FUNCTION_0 (void, f1)
    CREATE_DELEGATE_FUNCTION   (void, f2, const std::string&)
    CREATE_DELEGATE_FUNCTION   (int,  f3, int, int, int)
};

void f(const Holder& h) {
    h.f1();
    h.f2("world");
    cout << h.f3(2, 3, 4) << endl;
}

int main() {
    A obj1;
    f(Holder(&obj1));
    B obj2;
    f(Holder(&obj2));
}

(Следует отметить, что типы совпадают в вызовах CREATE_DELEGATE_FUNCTION проверяется во время компиляции).

Выход

A::f1
Hello world
9
B::f1
Shalom world
24

Макросы, стоящие за этой "магией":

#define NUM_ARGS(...) NUM_ARGS_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define NUM_ARGS_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N

#define CREATE_DELEGATE_FUNCTION_0(ret_type, name) \
   ret_type name (void) const {  \
   return boost::apply_visitor([](auto* const t) \
   {return t-> name ();}, _ptr); }

#define CREATE_DELEGATE_FUNCTION_1(ret_type, name, type1) \
   ret_type name (type1 a) const {  \
   return boost::apply_visitor([&a](auto* const t) \
   {return t-> name (a);}, _ptr); }

#define CREATE_DELEGATE_FUNCTION_2(ret_type, name, type1, type2) \
   ret_type name (type1 a, type2 b) const {  \
   return boost::apply_visitor([&a, &b](auto* const t) \
   {return t-> name (a, b);}, _ptr); }

#define CREATE_DELEGATE_FUNCTION_3(ret_type, name, type1, type2, type3) \
   ret_type name (type1 a, type2 b, type3 c) const {  \
   return boost::apply_visitor([&a, &b, &c](auto* const t) \
   {return t-> name (a, b, c);}, _ptr); }

#define M_CONC(A, B) M_CONC_(A, B)
#define M_CONC_(A, B) A##B

#define CREATE_DELEGATE_FUNCTION(ret_type, name, ...) \
   M_CONC(CREATE_DELEGATE_FUNCTION_, NUM_ARGS(__VA_ARGS__)) (ret_type, name, __VA_ARGS__)

Код: /questions/40123849/effektivno-poluchat-otsortirovannyie-summyi-iz-otsortirovannogo-spiska/40123864#40123864

Другие вопросы по тегам