смешивание шаблонов с полиморфизмом - PullRequest
0 голосов
/ 15 февраля 2019
class A
{
    friend void foo();
    virtual void print_Var() const{};

};// does not contain variable Var;


template<class T>
class B : public A
{
    T Var;
public:
    B(T x):Var(x){}
    void print_Var() const override
    {
        std::cout<<Var<<std::endl;
    }
};

void foo()
{
    std::array<std::unique_ptr<A>, 3> Arr = {
            std::make_unique<B<int>>(100),
            std::make_unique<B<int>>(20),
            std::make_unique<B<std::string>>("Hello Stackoverflow")
    };
            std::shuffle(Arr.begin(), Arr.end(), std::mt19937(std::random_device()())); // 3rd parameter generated by Clang-Tidy

    for (auto &i: Arr)
    {
        i->print_Var(); // OK
      //  auto z = i->Var   // no member named Var in A
                            // obviously base class does not contain such variable

     //   if (i->Var==20) {/* do something*/}
     //   if (i->Var=="Hello Stackoverflow") {/* do something*/}

    }
}

Объяснение: Я хочу перебрать массив указателей на A, который заполнен указателями на классы, производные от A, и в зависимости от того, какой тип является переменной Var, выполнить некоторое выражение if ().Проблема в том, что я не могу получить доступ к Var, потому что он не является членом базового класса.Однако эти значения можно упорядочить, например, перегруженной функцией, возвращающей void.Могу ли я написать функцию в классе, который возвращает шаблонный тип?как:

class A
{
    <class T> GetVar()
}

Кроме того, я чувствую, что имею дело с этой проблемой совершенно ненадлежащим образом.Могу ли я смешивать шаблоны и наследование таким образом?Если нет, то как это должно быть разработано?

Ответы [ 2 ]

0 голосов
/ 15 февраля 2019

Я думаю, что это будет самый простой способ.Просто переместите метод сравнения в интерфейс и переопределите его в производных классах.Добавьте следующие строки в ваш пример:

class A
{
   /*..................................................*/
   virtual bool comp(const int) const { return false; }
   virtual bool comp(const std::string) const { return false; }
   virtual bool comp(const double) const { return false; }  
};

template<class T>
class B : public A
{
   /*..................................................*/
   virtual bool comp(const T othr) const override { return othr == Var; }
};

void foo()
{
      /*..................................................*/
      if (i->comp(20))
      {
         /* do something*/
      }

      if (i->comp("Hello Stackoverflow"))
      {
         /* do something*/
      }
      /*..................................................*/
}
0 голосов
/ 15 февраля 2019

У вас есть несколько вариантов.Сначала я объясню свое предпочтительное решение.

1.Используйте динамическую диспетчеризацию

Если у вас есть массив типа базового класса, почему вы вообще хотите что-то делать с Var?Эта переменная специфична для дочернего класса.Если у вас где-то есть A, вас не должно волновать, что B имеет или не имеет в этом месте.

Операции над типизированной переменной должны быть заключены в виртуальную функцию в базовом классе.Если вы хотите выполнить условие и прочее, возможно, вы могли бы инкапсулировать это условие в виртуальную функцию, которая возвращает логическое значение.

2a.Отбросьте базовый класс и используйте вариант

Иногда вы заранее знаете количество типов, которые попадут в этот список.Использование варианта и отбрасывание базового класса - хорошее решение, которое может применяться к вашему делу.

Допустим, у вас есть только int, double и std::string:

using poly = std::variant<B<int>, B<double>, B<std::string>>;

std::array<poly, 3> arr;

arr[0] = B<int>{};
arr[1] = B<double>{};
arr[2] = B<std::string>{};
// arr[2] = B<widget>{}; // error, not in the variant type

std::visit(
    [](auto& b) {
        using T = std::decay_t<decltype(b)>;
        if constexpr (std::is_same_v<B<int>, T>) {
            b.Var = 2; // yay!
        }
    },
    arr[0]
);

2b.Отбросьте базовый класс и используйте универсальные функции

Полностью удалите базовый класс и задайте шаблоны для своих функций, которые над ними работают.Вы можете переместить все свои функции в интерфейс или множество std::function.Управляйте этим вместо прямой функции.

Вот пример того, что я имел в виду:

template<typename T>
void useA(T const& a) {
    a.Var = 34; // Yay, direct access!
}

struct B {
    std::function<void()> useA;
};

void createBWithInt() {
    A<int> a;
    B b;

    b.useA = [a]{
        useA(a);
    };
};

Это хорошо для случаев, когда у вас всего несколько операций.Но это может быстро привести к раздуванию кода, если у вас много операций или если у вас много типов std::function.

3.Использовать посетителя

Вы можете создать посетителя, который будет отправлять сообщения нужного типа.

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

Примерно так:

struct B_Details {
protected:
    struct Visitor {
        virtual accept(int) = 0;
        virtual void accept(double) = 0;
        virtual void accept(std::string) = 0;
        virtual void accept(some_type) = 0;
    };

    template<typename T>
    struct VisitorImpl : T, Visitor {
        void accept(int value) override {
            T::operator()(value);
        }

        void accept(double) override {
            T::operator()(value);
        }

        void accept(std::string) override {
            T::operator()(value);
        }

        void accept(some_type) override {
            T::operator()(value);
        }
    };
};

template<typename T>
struct B : private B_Details {
    template<typename F>
    void visit(F f) {
        dispatch_visitor(VisitorImpl<F>{f});
    }

private:
    virtual void dispatch_visitor(Visitor const&) = 0;
};

// later

B* b = ...;

b->visit([](auto const& Var) {
    // Var is the right type here
});

Затем, конечно, вам нужно реализовать dispatch_visitor для каждого дочернего класса.

4.Используйте std::any

Это буквально возвращает переменную с типом стирания.Вы не можете выполнить какие-либо операции с ним, не приведя его обратно:

class A {
    std::any GetVar()
};

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...