简体   繁体   中英

Virtual template workaround without too much verbosity

I am looking for a workaround to the lack of virtual template functions in C++. What I want ideally is to be able to store my derived classes in a vector, iterate over those and call the correct function, so in pseudo-code:


template<typename T>
struct Output
{
    ...
};

struct Base
{
    template<typename T>
    virtual void doSomething(Output<T>& out) = 0;
};

struct DerivedA : public Base
{
    DerivedA(const char* filename) {...}
    template<typename T>
    void doSomething(Output<T>& out) final
    {
        ...
    }
};

struct DerivedB : public Base
{
    DerivedB(const char* filename) {...}
    template<typename T>
    void doSomething(Output<T>& out) final
    {
        ...
    }
};

int main()
{
    std::vector<Base*> vec;
    vec.push_back(new DerivedA("data1.bin"));
    vec.push_back(new DerivedB("data2.bin"));
    vec.push_back(new DerivedA("data3.bin"));
    vec.push_back(new DerivedA("data4.bin"));

    Output<float> outF;
    Output<double> outD;
    Output<int> outI;
    for (auto e : vec)
    {
        e->doSomething(outF);
        e->doSomething(outD);
        e->doSomething(outI);
    }

    return 0;
}

I would prefer it if the workaround is as "painless" and non-verbose as possible (since I am using the templates to avoid redefining the same function n times for n different types in the first place). What I had in mind was making myself a vtable with std::map, and doing some dynamic_casts. I am looking for any better ideas, or even for a concise implementation of that idea if you consider it the best in this scenario. I am looking for a solution that is ideally the least intrusive, and that is very easy to add new classes to.

Edit: I figured a workaround, but it includes some verbosity (but at least avoids non-trivial code duplication):

struct Base
{
    virtual void doSomething(Output<int>& out) = 0;
    virtual void doSomething(Output<float>& out) = 0;
    virtual void doSomething(Output<double>& out) = 0;

private:
    template<typename T>
    void doSomething(Output<T>& out)
    {
        std::cout << "Base doSomething called with: " << typeid(T).name() << "\n";
    }
};

struct DerivedA : public Base
{
    void doSomething(Output<int>& out) final
    {
        doSomething<int>(out);
    }
    void doSomething(Output<float>& out) final
    {
        doSomething<float>(out);
    }
    void doSomething(Output<double>& out) final
    {
        doSomething<double>(out);
    }
private:
    template<typename T>
    void doSomething(Output<T>& out)
    {
        std::cout << "DerivedA doSomething called with: " << typeid(T).name() << "\n";
    }
};

struct DerivedB : public Base
{
    void doSomething(Output<int>& out) final
    {
        doSomething<int>(out);
    }
    void doSomething(Output<float>& out) final
    {
        doSomething<float>(out);
    }
    void doSomething(Output<double>& out) final
    {
        doSomething<double>(out);
    }
private:
    template<typename T>
    void doSomething(Output<T>& out)
    {
        std::cout << "DerivedB doSomething called with: " << typeid(T).name() << "\n";
    }
};

Does anybody have any better idea how I can go about this without having to redefine the same functions over and over? Ideally it would be defined once in the base class, CRTP doesn't seem to help. Dynamic casts seem like the other sane option.

Try something like this:

struct OutputBase
{
    virtual void doSomething() = 0;
};

template<class T >
struct Output : public OutputBase
{
    virtual void doSomething()
    {
        std::cout << typeid(T).name();
    }
};


struct Base
{
    virtual void doSomething(OutputBase* out) = 0;
};

struct DerivedA : public Base
{
    virtual void doSomething(OutputBase* out)
    {
        std::cout << "DerivedA doSomething called with: ";
        out->doSomething();
        std::cout<< std::endl;
    }
};

struct DerivedB : public Base
{
    virtual void doSomething(OutputBase* out)
    {
        std::cout << "DerivedB doSomething called with: ";
        out->doSomething();
        std::cout << std::endl;
    }
};
int main()
{
    OutputBase* out_int = new Output < int > ;
    OutputBase* out_double = new Output < double >;
    Base* a = new DerivedA;
    a->doSomething(out_int);
    a->doSomething(out_double);
    Base* b = new DerivedB;
    b->doSomething(out_int);
    b->doSomething(out_double);

    return 0;
}

You can use a wrapper around Output if you don't want to change it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM