user152508
user152508

Reputation: 3141

boost::variant and polymorphism

I want to get pointer to base class from boost variant, if I put orignally pointer to derived class. Is there some way to achive this . The following code does not work.

class A{ public: virtual ~A(){}}; class B : public A{};
typedef boost::variant<A*,B*> MyVar;
MyVar var = new B;
A* a = boost::get<A*> (var); // the following line throws exception

Maybe someone have idea how to write my own get function which will test if the requested type is base class of the stored type of in the variant,and then do the appropriate cast

Upvotes: 7

Views: 2225

Answers (2)

user152508
user152508

Reputation: 3141

Hi thank you all for your answers and comments I came to the following which decides at compile time if types are inherited from each other. And it seems to work, and it seems much easier to me to understand.

 #include <iostream>
 #include <boost/variant.hpp>
 #include <boost/type_traits.hpp>
 #include <boost/utility.hpp>

 using namespace boost::type_traits;


 struct A { virtual ~A() {} virtual void foo() {} };
 struct B : A { virtual void foo() { std::cout << "B::foo()" << std::endl; } };

  typedef boost::variant<B*,A*,C*> MyVar;


template <typename A,typename B> 
struct types_are_inheritance_related
{ 
 static const bool value=     
 ice_or<
 boost::is_base_of<A, B>::value,
 boost::is_base_of<B, A>::value
 >::value;    
};


 template<class Base>
 class get_visitor
: public boost::static_visitor<Base*> { public:


template<class T>
Base* operator()( T* t, typename boost::enable_if<types_are_inheritance_related<Base,T> >::type* dummy = 0)
{
   Base* b = dynamic_cast<Base*> ( t);
   return b;           
}  

template<class T>
Base* operator()( T* t, typename boost::disable_if<types_are_inheritance_related<Base,T> >::type* dummy = 0)
{       
   return 0;        
}   
};

template<class T>
T* get_var_value(MyVar& var)
{
   get_visitor<T> visitor;
   T* aa= var.apply_visitor(visitor);
   return aa;
}

int main()
{    
 MyVar var = new B;

 A* a = get_var_value<A*>(var); // works!
 a->foo();

 B* b = get_var_value<B*>(var); // works!
 b->foo();
}

Upvotes: 2

Piotr Skotnicki
Piotr Skotnicki

Reputation: 48517

You can write your own visitor with templated operator() like below:

LIVE DEMO

#include <iostream>
#include <boost/variant.hpp>
#include <type_traits>

struct A { virtual ~A() {} virtual void foo() {} };
struct B : A { virtual void foo() { std::cout << "B::foo()" << std::endl; } };

template <typename T>
struct visitor : boost::static_visitor<T>
{
private:
    using Base = typename std::remove_pointer<
                        typename std::remove_cv<
                            typename std::remove_reference<T>::type
                        >::type
                    >::type;

    template <typename U>
    T get(U& u, std::true_type) const
    {
        return u;
    }

    template <typename U>
    T get(U& u, std::false_type) const
    {
        throw boost::bad_get{};
    }

public:
    template <typename U>
    T operator()(U& u) const
    {
        using Derived = typename std::remove_pointer<
                            typename std::remove_cv<
                                typename std::remove_reference<U>::type
                            >::type
                        >::type;

        using tag = std::integral_constant<bool
                         , (std::is_base_of<Base, Derived>::value
                           || std::is_same<Base, Derived>::value)
                           && std::is_convertible<U, T>::value>;

        return get(u, tag{});
    }
};

template <typename T, typename... Args>
T my_get(boost::variant<Args...>& var)
{
    return boost::apply_visitor(visitor<T>{}, var);
}

int main()
{    
    boost::variant<A*,B*> var = new B;

    A* a = my_get<A*>(var); // works!
    a->foo();

    B* b = my_get<B*>(var); // works!
    b->foo();
}

Output:

B::foo()
B::foo()

Q & A section:

This solution is weird!

No, it is not. This is exactly what the visitor classes in Boost.Variant are for. Similar solution already exists in latest release of Boost.Variant, which is boost::polymorphic_get<T>. Sadly it was designed for other purposes and cannot be used here.

Upvotes: 10

Related Questions