Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
419 views
in Technique[技术] by (71.8m points)

c++ - mixing templates with polymorphism

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*/}

    }
}

Explanation: I want to iterate over array of pointers to A, which is filled with pointers to classes derived from A, and depending on what type is variable Var, do some if( ) statement. Problem is that i cannot access Var, cause its not member of base class. However, it's possible to cout those values by, for example, overloaded function returning void. Could i write function in A class that returns templated type? like:

class A
{
    <class T> GetVar()
}

Besides, I feel like I'm dealing with this problem in totally improper way. Can i mix templates and inheritance like that? If not, how should it be designed?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...