mixing templates with polymorphism

前端 未结 2 554
逝去的感伤
逝去的感伤 2020-12-02 01:07
class A
{
    friend void foo();
    virtual void print_Var() const{};

};// does not contain variable Var;


template
class B : public A
{
    T Var;         


        
相关标签:
2条回答
  • 2020-12-02 01:41

    I think it will be the easiest way. Just move the comparison method to the interface and override it in derived classes. Add the following lines to yor example:

    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 讨论(0)
  • 2020-12-02 01:44

    You have a few choices. I'll explain my preferred solution first.

    1. Use dynamic dispatch

    If you have an array of a base class type, why do you even want to do stuff with Var? That variable is specific to the child class. If you have a A somewhere, you shouldn't even care what B has or hasn't at that place.

    Operations on the typed variable should be encapsulated in virtual function in the base class. If you want to do condition and stuff, maybe you could encapsulate that condition into a virtual function that returns a boolean.

    2a. Drop the base class and use variant

    Sometimes, you know in advance the amount of types that will go into that list. Using a variant and drop the base class is a good solution that may apply to your case.

    Let's say you only have int, double and 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. Drop the base class and use generic functions

    Drop the base class entirely, and template your functions that do operation on them. You can move all your function into an interface or many std::function. Operate on that instead of the function directly.

    Here's an example of what I meant:

    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);
        };
    };
    

    This is fine for cases where you only have few operations. But it can quickly lead to code bloat if you have a lot of operations or if you have many types of std::function.

    3. Use a visitor

    You could create a visitor that dispatch to the right type.

    This solution would be much close to what you except, but is quite combersome and can break easily when adding cases.

    Something like this:

    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
    });
    

    Then of course, you have to implement the dispatch_visitor for each child class.

    4. Use std::any

    This is litteraly returning the variable with type erasure. You cannot do any operation on it without casting it back:

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

    I personnaly don't like this solution because it can break easily and is not generic at all. I would not even use polymorphism in that case.

    0 讨论(0)
提交回复
热议问题