3

I'm using boost-variant throughout my projects. In one scenario I need to classify the types contained in my boost-variant into several classes. As I have quite a lot of types in my variant I came up to the idea of defining several variants inside my visitor. These variants are basically the type->class mapping.

The code below illustrates what I wanted to achieve.

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

    enum class Type {
        Integer,
        Float,
        NonNumeric
    };

    struct IsNum : public boost::static_visitor<Type> {
        typedef boost::variant<int, size_t> IntegerTypes;
        typedef boost::variant<float, double> FloatTypes;
        typedef boost::variant<std::string> NonNumericTypes;

        result_type operator()(const IntegerTypes& t) const {
            return Type::Integer;
        }

        result_type operator()(const FloatTypes& t) const {
            return Type::Float;
        }

        result_type operator()(const NonNumericTypes& t) const {
            return Type::NonNumeric;
        }
    };

    int main() {
        boost::variant<int, std::string, double> value;
        value = 5;
        IsNum visitor;
        auto result = value.apply_visitor(visitor);
    }

Unfortunately, the code will not compile. MSVC ends with the compiler error C3066. There are different possibilities to call an object of this type with this arguments? It might be one of the three operator() functions.

But basically I can convert 5 only to the variant type IntegerTypes.

What might be the solution for this kind of problem?

Own Solution

After trying to use boost-mpl I arrived at this solution. The function Contains represents a piece of reusable software, that might be included into other pieces of my program. Still I wished the solution would be closer to my original posted source code.

#include <iostream>
#include <boost/variant.hpp>
#include <string>
#include <boost/mpl/contains.hpp>

enum class Type {
    Integer,
    Float,
    NonNumeric
};

template<typename V, typename T>
using ContainsImpl = typename boost::mpl::contains<typename V::types, T>::type;

template<typename V, typename T>
bool Contains(const T&) {
    return ContainsImpl<V, T>::value;
}

struct IsNum : public boost::static_visitor<Type> {
    typedef boost::variant<int, size_t> IntegerTypes;
    typedef boost::variant<float, double> FloatTypes;
    typedef boost::variant<std::string> NonNumericTypes;

    template<typename T>
    result_type operator()(const T& t) const {
        if (Contains<IntegerTypes>(t)) {
            return Type::Integer;
        } else if (Contains<FloatTypes>(t)) {
            return Type::Float;
        } else if (Contains<NonNumericTypes>(t)) {
            return Type::NonNumeric;
        }

        return Type::NonNumeric;
    }
};

int main() {
    boost::variant<int, std::string, double> value;
    value = 5.;
    IsNum visitor;
    auto result = value.apply_visitor(visitor);
    if (result == Type::Integer) {
        std::cout << "Integer" << std::endl;
    }
    if (result == Type::Float) {
        std::cout << "Float" << std::endl;
    } 
    if (result == Type::NonNumeric) {
        std::cout << "Non Numeric" << std::endl;
    }
}
Aleph0
  • 5,816
  • 4
  • 29
  • 80

1 Answers1

2
#include <string>
#include <boost/variant.hpp>
#include <boost/mpl/list.hpp>
#include <boost/mpl/contains.hpp>
#include <boost/type_traits.hpp>

enum class Type
{
    Integer,
    Float,
    NonNumeric
};

struct IsNum : public boost::static_visitor<Type>
{
    typedef boost::mpl::list<int, size_t> IntegerTypes;
    typedef boost::mpl::list<float, double> FloatTypes;
    typedef boost::mpl::list<std::string> NonNumericTypes;

    template <typename T>
    typename boost::enable_if<boost::mpl::contains<IntegerTypes, T>, result_type>::type
    operator()(const T& t) const
    {
        return Type::Integer;
    }

    template <typename T>
    typename boost::enable_if<boost::mpl::contains<FloatTypes, T>, result_type>::type
    operator()(const T& t) const
    {
        return Type::Float;
    }

    template <typename T>
    typename boost::enable_if<boost::mpl::contains<NonNumericTypes, T>, result_type>::type
    operator()(const T& t) const
    {
        return Type::NonNumeric;
    }
};

DEMO

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • Cool. I also derived this solution now. It seems to be the only workable approach. But reading it might get you headache. :-) I'll post my solution also. Many thanks. – Aleph0 Mar 02 '17 at 08:19
  • 1
    @FrankSimon more alternatives include: tag-dispatching and adding a separate overload for each type – Piotr Skotnicki Mar 02 '17 at 08:21
  • No idea what tag-dispatching is. I'll think that I have to learn a bit more. :-) – Aleph0 Mar 02 '17 at 08:22