2

In one of my projects I'm actively using boost::variant and I stumbled upon a question I couldn't resolve on my own. I have a boost::variant that might contain atomic datatypes and STL containers of these atomic datatypes.

Now, I wanted to compute the size of an instance of the previously defined boost::variant type. There are basically only two possible functions. The type of an atomic datatype is simply 1, whereas the size of an STL container is defined as the number of elements contained in it.

Having only 2 atomic dataypes I implemented the following code:

#include <boost/variant.hpp>
#include <string>
#include <iostream>
#include <vector>

typedef boost::variant<int, double, std::vector<int>, std::vector<double> > TVariant;

struct sizeVisitor : boost::static_visitor<size_t> {
   size_t operator()(int&) {
    return 1;
    }
   size_t operator()(double&) {
    return 1;
    }

   size_t operator()(std::vector<int>& c) {
    return c.size();
    }

   size_t operator()(std::vector<double>& c) {
    return c.size();
    }
} ;


int main(int argc, char **args) {
    sizeVisitor visitor;
    TVariant var=5;
    std::cout << boost::apply_visitor(visitor, var) << std::endl;
    std::vector<int> vector;
    vector.push_back(6);
    vector.push_back(2);
    var=vector;
    std::cout << boost::apply_visitor(visitor, var) << std::endl;
}

As the number of atomic datatypes increases I have a lot of code duplication. I have to declare another two functions for every new atomic datatype, which can be prohibitive.

It would be nice, if the following code would compile:

#include <boost/variant.hpp>
#include <string>
#include <iostream>
#include <vector>

typedef boost::variant<int, double, std::vector<int>, std::vector<double> > TVariant;

struct sizeVisitor : boost::static_visitor<size_t> {
   size_t operator()(boost::variant<int,double>&) {
    return 1;
    }

   size_t operator()(boost::variant<std::vector<int>,std::vector<double>>& c) {
    return c.size();
    }

} ;


int main(int argc, char **args) {
    sizeVisitor visitor;
    TVariant var=5;
    std::cout << boost::apply_visitor(visitor, var) << std::endl;
    std::vector<int> vector;
    vector.push_back(6);
    vector.push_back(2);
    var=vector;
    std::cout << boost::apply_visitor(visitor, var) << std::endl;
}

What might be the closest implementation of the second, unfortunately non-compiling, visitor?

Aleph0
  • 5,816
  • 4
  • 29
  • 80

1 Answers1

5

Just use two function templates for operator():

struct sizeVisitor 
    : boost::static_visitor<size_t>
{
    template <class T>
    size_t operator()(T const&) {
        return 1;
    }

    template <class T>
    size_t operator()(std::vector<T> const& v) {
        return v.size();
    }    
};

The template partial ordering rules will ensure that the correct one is called.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Wow. That's a very obvious solution. Many thanks. I'm wondering if there is also a solution for the more general problem, where I have to divide the set of types of a given `boost::variant` into two subsets, e.g. into numeric and non-numeric. And I wanted to write a visitor yielding a boolean for this property. – Aleph0 Jun 03 '16 at 18:47
  • @FrankSimon Use SFINAE - one overload enabled and one disabled depending on some type trait. – Barry Jun 03 '16 at 18:52
  • I'm not aware of that concept. Seems that I have to learn a little more about C++. Many thanks for these deep insights. I'll take a look at it. – Aleph0 Jun 03 '16 at 18:53
  • @FrankSimon Enjoy the rabbit hole you're about to fall into :) – Barry Jun 03 '16 at 19:01
  • 1
    @frank rather than sfinae, tag dispatch from primary `()` to helper method. Less confusing, easier, easier to read errors. `{ return helper(std::is_numeric{}, t); }`, where `helper(true_type, T)` gets the numerics and `helper(false_type, T)` gets the rest. – Yakk - Adam Nevraumont Jun 03 '16 at 19:03