7

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

Craig McQueen
  • 41,871
  • 30
  • 130
  • 181
user152508
  • 3,053
  • 8
  • 38
  • 58

2 Answers2

10

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.

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • That button is gonna be a precedent. Shame about coliru not running most of my answers (too heavy) – sehe Sep 13 '14 at 10:11
  • One up vote here is to mark the number of people in the world thinking this the way to approach the problem. Including OP. – SChepurin Sep 13 '14 at 15:37
  • @SChepurin: feel free to present your solution – Piotr Skotnicki Sep 13 '14 at 15:40
  • @Piotr S. - I didn't say your solution is bad. It is simply weird solution. – SChepurin Sep 13 '14 at 16:14
  • Would be nicer to use `is_base_of` and `is_convertible` at compile-time, eg. with `enable_if`. – Igor R. Sep 13 '14 at 17:30
  • @IgorR.: yes, but the current solution is more in *variant fashion*, which throws exception on type mismatch – Piotr Skotnicki Sep 13 '14 at 17:33
  • You could throw with CT solution as well: in one overload you just `return u;`, in another one just `throw std::runtime_error`. This will eliminate the runtime branching, and also betters complies with the `visitor` "spirit". – Igor R. Sep 13 '14 at 17:36
  • @Piotr S. - "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" - don't overreact. That is exactly what i had in mind - Boost.Variant "with visitor "spirit" is a weird construction. – SChepurin Sep 14 '14 at 07:47
  • Hi all. @Piotr S. thank you for your solution. It is not weird at all, however I manage to rework it, and maybe get an easier and more understandable version of it, at least for me. I will post it as an answer. – user152508 Sep 23 '14 at 13:29
  • Hello, I'd like to know what are the "design purposes" of the official boost `polymorphic_get` if its not that ? because that sounds potentially very misleading then. – v.oddou Apr 10 '15 at 06:29
  • 1
    @v.oddou *"The function succeeds only if the content is of the specified type U or of type derived from type U"*. Here OP is storing *pointers* in the variant object. A pointer type is never derived from any other type. – Piotr Skotnicki Apr 10 '15 at 07:04
2

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();
}
user152508
  • 3,053
  • 8
  • 38
  • 58