views:

179

answers:

2

In my C++ library I have a type boost::variant<A,B> and lots of algorithms getting this type as an input. Instead of member functions I have global functions on this type, like void f( boost::variant<A,B>& var ). I know that this can also be achieved with templates, but this is not suitable for my design.

I am very fine with this style of programming:

boost::variant<A, B> v;
f( v );

but some of the users of this library are not used to it, and since the Boost.Variant concept is hidden by a type definition, they feel like calling v.f().

To achieve this, I can think of two possibilities: 1) overriding from boost::variant and 2) re-implementing boost::variant and adding my own member functions. I am not sure whether these ideas are good or not. Can you give me some help with this please? Are there other possibilities?

A: 

I'd derive from boost::variant. That should be fine so long as you dont add data members to the class, and don't add virtual functions. (You may be able to do some of those but things are a little more iffy I think). Anyway this seems to work OK for me.

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

template<typename T1, typename T2>
struct my_print : public boost::static_visitor<>
{
  void operator()( T1 t1 ) const
  {
    std::cout<<"FIRST TYPE "<<t1<<std::endl;
  }
  void operator()( T2 t2 ) const
  {
    std::cout<<"SECOND TYPE "<<t2<<std::endl;
  }
};

template<typename T1, typename T2>
class MyVariant : public boost::variant<T1,T2>
{
  public:
    void print()
    {
      boost::apply_visitor(my_print<T1,T2>(), *this );
    }

    template<typename T>
    MyVariant<T1,T2>& operator=(const T& t)
    {
      boost::variant<T1,T2>::operator=(t);
      return *this;
    }

    MyVariant(const T1& t) : boost::variant<T1,T2>(t)
    {
    }

    MyVariant(const T2& t) : boost::variant<T1,T2>(t)
    {
    }

    template<typename T>
    explicit MyVariant(const T& t) : boost::variant<T1,T2>(t)
    {
    }
};

int main()
{
  MyVariant<int,std::string> s=1;
  s.print();
  s=std::string("hello");
  s.print();

  MyVariant<int,std::string> v2 = s;
  v2.print();

  s=boost::variant<int,std::string>(3);
  s.print();
}
Michael Anderson
+2  A: 

Another possibility: Use aggregation. Then you do not directly expose the boost.variant to the users of the library, giving you way more freedom for future improvements, and may simplify some debugging tasks by a significant amount.

General Advice: Aggregation is less tightly coupled than inheritance, therefore better by default, except you know a use-case where you explicitly want to pass your object instance to already existing functions only taking variants. And even than the base class should have been designed with inheritance in mind.

Example for Aggregation for Your Problem: As far as I understand it, the free functions already exist, and take a variant. Just define a class with the sole data member of the variant, and provide public member functions which do nothing but invoke the already existing free functions with the member variant, like

class variant_wrapper {
  boost::variant<A,B> m_variant;
public:
  variant_wrapper(...) : m_variant(...) {} // whatever c_tor you need.
  void f() {  f(m_variant); }
};

Using this approach you abstract away the fact that you are using boost.variant for your implementation (which you already do through a typedef for the library's users), giving you the freedom of later changing that (for optimization or feature extensions or whatever), you can decide to make the values immutable, have a more simple approach to debug accesses to your algorithms, etc. etc..

The disadvantage with the aggregation is that you cannot just pass the wrapper to a static_visitor, but as your users shall not know that there is a variant, and you know to simply pass the member variable, I do not see a big issue here.

Final rant: C++ is not Java. You need to fix the users of the library...

What you would like to have are C# extension methods; such things do not exist in C++. However, I would not reimplement/implementation-copy boost.variant (maintenance burden), and I would not inherit from it. Use aggregation where possible.

gimpf
Thanks, this solution sounds very good and I totally understand the rant :) For me it makes sense as well to use global functions in that case.
Rupert Jones