Имитиране на динамичен полиморфизъм с CRTP + променливи шаблони

Трябва да заменя динамичния полиморфизъм в код с доста големи класове и имам проблеми с дизайна на CRTP + променливи шаблони.

За да илюстрирам, предоставям проблем с играчка по-долу.

Има динамично полиморфен клас, който използва следния интерфейс:

int main() { 
  Loop loop;
  Add add ;
  loop.set(add);
  loop.run();
  Sub sub ;
  loop.set(sub); 
}

Разбира се, Sub и Add са извлечени от базов клас Operation, а op(double & v) е виртуален метод.

Трябва да запазя този интерфейс по други причини, докато променям динамичния чрез статичен полиморфизъм, това е моето решение до момента:

template <typename T> class Operation { 
public: 
  double d=1.;
  void op(double & d) { static_cast<T*>(this)->op(d);}
};
class Add final: public Operation<Add> { 
public: void op(double & v) { v += d; }
};
class Sub final: public Operation<Sub> {
public: void op(double & v) { v -= d; }
};

и за клас Loop:

template <typename... Types>
class MetaLoop {
public:
  double d = 0 ;
  template <typename T> void set(T & t) { 
  std_cxx17::get<std::vector<T*> >(data).push_back(&t) ; 
  }
  template <typename T> void run() { 
    for (auto j = 0; j<2E9 ; ++j)
      std_cxx17::get<std::vector<T*> >(data).back()->op(d);
    std::cout << d << std::endl; 
  }
private:
  std::tuple<std::vector<Types*>... > data ;
};
typedef MetaLoop<Add,Sub> Loop ;

std_cxx17::get<T*>(data) е извикване на метод, избиращ елементи в кортеж по тип, подобно на тази публикация

Това решение обаче изисква почти интерфейса, който искам, с изключение на необходимостта от предоставяне на аргумента на шаблона на run(), т.е. run<Add> и run<Sub> и необходимостта от регистриране Add и Sub на MetaLoop в typedef MetaLoop<Add,Sub> Loop

Бих искал да знам дали е възможно, без да използвам макроси, да:

  1. Регистрирайте Operation<T> класовете в MetaLoop автоматично (т.е. автоматично извикване на typedef Metaloop<...operations...>)
  2. Избягвайте да предоставяте шаблонния клас на метода run. (т.е. обадете се на run() вместо на run<Add>())
  3. Вземете тези методи от библиотека: Няма ли библиотека (напр. Boost), която може да осигури този вид полиморфизъм статично?

Моля, имайте предвид, че съм наясно, че има няколко други архитектури, които мога да използвам, но има много други ограничения, с които трябва да си играя. Това е просто проблем с играчката за пример.

Благодаря предварително!

РЕДАКТИРАНЕ 1:

Въпрос 2 е отговорен! Вижте тук: http://coliru.stacked-crooked.com/a/e68ea7faabba3e40

Поздравления за https://www.reddit.com/user/17b29a


person Astor    schedule 25.09.2016    source източник
comment
Какво очаквате да работи, ако не посочите изрично Add? Трябва ли да работи както Add, така и Sub?   -  person skypjack    schedule 26.09.2016
comment
Очаквам да изпълнява последния зададен тип. Благодаря!   -  person Astor    schedule 26.09.2016
comment
И така, очаквате да имате само един активен тип всеки път? Защо съхранявате всичко във вектор по този начин?   -  person skypjack    schedule 26.09.2016
comment
Страхотен въпрос! Благодаря за отговора! Съхранявам всичко във вектор, защото очаквам да съхраня повече от едно инстанциране на Add например.   -  person Astor    schedule 26.09.2016
comment
Добре, но ако вашата грижа е да използвате винаги и само последния, защо следите останалите? Не можете ли просто да смените активния? (Питам, за да мога да ви отговоря правилно).   -  person skypjack    schedule 26.09.2016
comment
Трябва да следя другите, защото може да искам да задам предишна инстанция на Add, в който случай ще трябва да сравня, преди да изпълня run(). Надявам се, че все още може да се изпълнява статично.   -  person Astor    schedule 26.09.2016


Отговори (1)


Можете да изтриете типовете вашите типове, както в следния пример:

#include <iostream>
#include <vector>
#include <utility>

template<typename T>
struct Operation {
    void op(double &d) { static_cast<T*>(this)->op(d) ; }
    double d{1};
};

struct Add: Operation<Add> {
    void op(double &v) { v += d; }
};

struct Sub: Operation<Sub> {
    void op(double &v) { v -= d; }
};

class Loop {
    using Fn = void(*)(double &, void *);

    template<typename T>
    static void op(double &d, void *op) {
        static_cast<T*>(op)->op(d);
    }

public:
    template<typename T>
    void set(T &t) {
        data.emplace_back(&op<T>, &t);
    }

    void run() {
         auto &p = data.back();
         for(auto j = 0; j < 10; ++j) {
             p.first(d, p.second);
         }

         std::cout << d << std::endl;
    }

private:
    std::vector<std::pair<Fn, void*>> data;
    double d = 0;
};

int main() {
    Loop loop;
    Add add;
    loop.set(add);
    loop.run();
    Sub sub;
    loop.set(sub);
    // ...
}

Обърнете внимание също, че в този случай CRTP идиомът е безполезен и можете да изпуснете шаблонния клас Operation.
Просто дефинирайте Add и Sub, както следва:

struct Add {
    void op(double &v) { v += d; }
    double d{1};
};

struct Sub {
    void op(double &v) { v -= d; }
    double d{1};
};

Ако искате да направите разлика между Add и Sub и да предоставите различни алгоритми, можете да използвате специализации.
Като пример:

class Loop {
    using Fn = void(*)(double &, void *);

    template<typename T>
    static void op(double &d, void *op);

public:
    template<typename T>
    void set(T &t) {
        data.emplace_back(&op<T>, &t);
    }

    void run() {
        auto &p = data.back();
        for(auto j = 0; j < 10; ++j) {
            p.first(d, p.second);
        }

        std::cout << d << std::endl;
    }

private:
    std::vector<std::pair<Fn, void*>> data;
    double d = 0;
};

template<>
void Loop::op<Add>(double &d, void *op) {
    Add *add = static_cast<Add*>(op);
    // do whatever you want with add
    add->op(d);
}

template<>
void Loop::op<Sub>(double &d, void *op) {
    Sub *sub = static_cast<Sub*>(op);
    // do whatever you want with sub
    sub->op(d);
}

Имайте предвид, че решението отговаря на всички изисквания, изброени във въпроса, и работи с main, предоставен като пример от OP.

person skypjack    schedule 26.09.2016
comment
Благодаря @skypjack! За съжаление моите обекти Add и Sub са много по-сложни, отколкото е показано в моя пример и това, което правя с тях в Loop, също е много по-сложно от показаното. Следователно наистина имам нужда от достъп до указатели към тях. Получих отговор за коригиране на интерфейса, който според мен е доста чист! Сега просто ми трябва начин да инстанцирам всички Operation<T> специализации и всичко ще бъде решено! :-) - person Astor; 26.09.2016
comment
@Astor В моя пример вие всъщност осъществявате достъп до указател към тях. Вижте реализацията на метода op в Loop, той връща обектите обратно към техните първоначални типове. - person skypjack; 26.09.2016
comment
@Astor Добави повече подробности към отговора. Кажете ми, ако е ясно. Все още можете да използвате CRTP, ако искате, въпреки че не е строго задължителен в този случай. - person skypjack; 26.09.2016
comment
Вие го правите, но ще трябва да съхранявам толкова Fn функции, колкото функциите-членове има моят Operation<T>, но най-важното нещо не е това, а фактът, че трябва да запазя статичен код и във вашия пример (доколкото моят компилатор е засегнат) указател се дереферира при всяко извикване на p.first(d, p.second) като в динамично полиморфен код. При повторение 2E9 пъти, той работи 3 пъти по-бавно. Все пак огромни благодарности, честно! Трябваше да съм по-ясен със спецификациите си. За момента просто трябва да намеря начин да регистрирам всички Operation<T> в MetaLoop... - person Astor; 26.09.2016
comment
@Astor Можете да преместите цикъла в специализираните функции и да получите справка, преди да го стартирате. Наистина не е голям проблем. Както и да е, това е вашият код, направете както предпочитате. Късмет. - person skypjack; 26.09.2016
comment
Благодаря много, наистина!! - person Astor; 26.09.2016