Adam Hunyadi
Adam Hunyadi

Reputation: 1952

Polymorphism in std::vector with non-member functions

I've already tried to post this problem, but everyone complained, how my problem is hard to understand and asked me to provide an MCVE, so I've decided to ask this question again with an example provided:

http://pastebin.com/SvidcrUi

I have a problem, that I came across. I am working in C++11 with Root, and I have a std::vector which contains variables (histograms) of type TH1D* and TH2D*. I am not allowed to touch the TH1D or TH2D definitions, but I am doing different things based on the type of the variable.

Whenever I want to call an overloaded function, that would handle both cases, my compiler cries out saying "call of overloaded function is ambiguous"... I know it is, but I need help figuring out a better design... How should I do it?

My problem in the code:

void save_histograms_from_vector( const std :: vector<TH1*>& histogram_vector_p )
{
    for( auto& histogram: histogram_vector_p )
    {
        save_histogram_as_canvas( histogram ); //overloaded function
    }
}
(...)
template<typename TH_type_1, typename TH_type_2, typename TH_type_3, typename TH_type_4> 
void save_as_two_by_two_canvas( TH_type_1 histogram_1_p, TH_type_2 histogram_2_p, TH_type_3 histogram_3_p, TH_type_4 histogram_4_p, TFile* output_file_p, const std :: string& directory_name_p, const std :: string& canvas_name_p, const std :: string& canvas_title_p )
{
     (...)
     const std :: vector<TH1*> histograms = { histogram_1_p, histogram_2_p, histogram_3_p, histogram_4_p };
     save_histograms_from_vector( histograms );
     // This would have worked if I called the function Write() for each of the histograms )
}

So, as for the example, my goal is to write functions that can achieve what the message_of_instances() function should do. The example now does not compile, but the only thing it has a problem with, is that it can not deduce the type of the elements in the std :: vector. If I were to call a member function of the elements, like simply write() works.

My question is: Is there a workaround for these kinds of problems?

Thank you for all the constructive comments!!

Upvotes: 0

Views: 236

Answers (2)

EmDroid
EmDroid

Reputation: 6066

You can use boost::variant to wrap the particular types (the boost variant then keeps track, which type it has been created from). Then either check for each value which type it actually is (boost::variant::which) or better yet a variant visitor to apply operations on the particular types.

Or you can indeed roll something simpler similar by yourself (basically a wrapper providing constructor for each possible type and keeping track which type it has been constructed from, that is what boost::variant does in the principle). Or use union (boost::variant is C++ replacement of unions).

EDIT: This is an example how it can be done without altering the classes. Basically introducing a wrapper which will store the type-erased implementation keeping track of the actual type (just wrote it quickly, might need some polishing):

class BaseWrapper
{
public:
    template<typename TH_TYPE>
    BaseWrapper(TH_TYPE *x)
        : impl(createImpl(x))
    {}
    BaseWrapper()
        : impl(nullptr)
    {}
    BaseWrapper(const BaseWrapper &other)
        : impl(cloneImpl(other.impl))
    {}
    BaseWrapper & operator =(const BaseWrapper &other)
    {
        if (this != &other)
        {
            ImplBase *newImpl = cloneImpl(other.impl);
            delete impl;
            impl = newImpl;
        }
        return *this;
    }
    ~BaseWrapper()
    {
        delete impl;
    }

    void doStuff() const
    {
        if (impl)
            impl->doStuff();
    }
private:

    class ImplBase {
    public:
        ImplBase(Base *x)
            : ptr(x)
        {}
        virtual ImplBase *clone() const = 0;
        virtual void doStuff() const = 0;
    protected:
        Base *ptr;
    };

    template<typename TH_TYPE>
    class Impl: public ImplBase {
    public:
        Impl(Base *x)
            : ImplBase(x)
        {}
        ImplBase *clone() const
        {
            return new Impl<TH_TYPE>(*this);
        }
        void doStuff() const
        {
            if (ptr)
                write_and_do_other_stuffs( static_cast<TH_TYPE *>(ptr) );
        }
    };

    template<typename TH_TYPE>
    static ImplBase *createImpl(TH_TYPE *x)
    {
        return new Impl<TH_TYPE>(x);
    }

    static ImplBase * cloneImpl(ImplBase *impl)
    {
        return impl ? impl->clone() : impl;
    }

    ImplBase *impl;
};

Then, use std::vector<BaseWrapper> instead of std::vector<Base *> and call doStuff which will forward the calls up to the BaseWrapper::Impl providing the real call with the correct type. Could be extended to provide RAII for the Base * as well, call the different methods through functors etc.

EDIT #2: With the boost::variant it would look like this:

#include <boost/variant.hpp>

typedef boost::variant<Derived_one *, Derived_two *> BaseVariant_t;

struct MyVisitor: boost::static_visitor<>
{
    template<typename TH_TYPE>
    void operator()(TH_TYPE * ptr) const
    {
        write_and_do_other_stuffs( ptr );
    }
};

void message_of_instances( const std :: vector<BaseVariant_t>& instances_p )
{
    for( auto it = instances_p.begin(); it != instances_p.end();++it )
    {
        boost::apply_visitor(MyVisitor(), *it);
    }
}

As you can see, far more elegant, however it has the limitation that you need to know all the possible types upfront (the boost::variant needs to know about all of them and they must be supplied as the template arguments). The above wrapper solution does not have that limitation, but that comes with a price - there are extra memory allocations (boost::variant does not need any extra memory allocations) and virtual method calls (boost::variant static_visitor uses the template machinery so the calls are direct).

Note that the visitor can either provide a global templated visit method (as it is in the example), or provide separate operator () for each type. Or even combine both (have separate operator for some types and templated solution for the rest).

In the fact, the wrapper solution can be extended as well to use a visitor (instead of having to provide extra methods to call each different method).

Upvotes: 1

Anon Mail
Anon Mail

Reputation: 4770

In order to properly utilize polymorphism, you need to have all of your functions declared in your base class which can then be overridden in the derived classes.

If that doesn't work, you can do one of the following:

  • Downcast your base class to derived classes using dynamic_cast.

  • Utilize the Visitor pattern. You can look up it's definition and use on this site and others.

Upvotes: 1

Related Questions