Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphic setter for Boost::variant

I am trying to use the boost::variant with template types. For example, I have a template type Tag<T> and the boost::variant AnyTag comprises types such as Tag<double>, Tag<int> and Tag<std::string>. Each Tag<T> has members of type T. Now, I would like to put those variants in a container and simply assign values during runtime, e.g.,

for(AnyTag & tag: AllTags) {
    setValue(tag, getValueFromXml()); 
}

The function setValue(AnyTag &tag, T &val) must use the runtime type of the AnyTag tag in order to correctly assign the tag with the correct value. My attempt to solving the problem can be found below and it makes use of another variant which included only the possible T types that could be used in the AnyTag (TagValueType).

template<typename T, typename = void>
class Tag {};

template <typename T>
class Tag<T, EnableIf<std::is_arithmetic<T>>> {
public:
    T value = 0;
    std::string address = "";
    T maxValue = std::numeric_limits<T>::max();
    typedef T value_type;
};

template <typename T>
class Tag<T, DisableIf<std::is_arithmetic<T>>> {
public:
    T value;
    std::string address = "";
    typedef T value_type;
};

typedef boost::variant<Tag<std::string>,
                       Tag<double>,
                       Tag<int>,
                      > AnyTag;

typedef boost::variant<std::string, double, int> TagValueType;

class tag_set_value_visitor: public boost::static_visitor<void>
{
    const TagValueType & value;
public:
    tag_set_value_visitor(const TagValueType & val): value(val){}
    template <typename T>
    void operator()(T & tag) const
    {
        tag.value = boost::get<typename T::value_type>(value);
    }
};

inline void setValue(AnyTag & tag, const TagValueType & val) {
    assert(tag.which() == val.which());
    boost::apply_visitor( tag_set_value_visitor(val), tag );
}

Unfortunately, this approach is not what I would like because for example during compilation there is not problem if I do the following:

AnyTag a = Tag<int>();
setValue(a, double(1.3));

but during runtime, the boost library detects the type mismatch and crashes the program.

So, my solution is kind of a type erasure that just postpones the problem.

What I would like to have is a setValue(AnyTag &tag, T &val) where T is the runtime type of the AnyTag.

I get that that's what the variant's visitor tries to do, but there is a problem in this case because when we construct the visitor we must know the type that we are going to use.

Any ideas or any thoughts about this problem?

P.S.: Sorry for the long post but I couldn't find a way to explain my thought process with fewer words.

like image 963
CuriousNik Avatar asked May 29 '15 23:05

CuriousNik


1 Answers

Use¹ a binary visitor.

Implement the operator() to do nothing except for corresponding types.

Handle mismatches to taste (I return a boolean indicating success):

Live On Coliru

#include <boost/any.hpp>
#include <boost/variant.hpp>
#include <boost/mpl/vector.hpp>
#include <string>

using namespace boost;

template <typename T>
struct Tag {
    T value;
};

using Types   = mpl::vector<std::string, double, int>;
using Tags    = mpl::transform<Types, Tag<mpl::_1> >::type;

using Variant = make_variant_over<Types>::type;
using AnyTag  = make_variant_over<Tags>::type;

namespace mydetail {
    struct assign_to : boost::static_visitor<bool> {
        template <typename V> bool operator()(Tag<V>& tagged, V const& value) const {
            tagged.value = value;
            return true;
        }

        template <typename T, typename V> bool operator()(T&&, V&&) const {
            return false;
        }
    };
}

bool setValue(AnyTag &tag, Variant const& val) {
    return boost::apply_visitor(mydetail::assign_to(), tag, val);
}

int main() {
    AnyTag t;

    t = Tag<std::string>();

    // corresponding type assigns and returns true:
    assert(setValue(t, "yes works"));

    // mismatch: no effect and returns false:
    assert(!setValue(t, 42));
    assert(!setValue(t, 3.1415926));
}

¹ If I understood your goal correctly. I've focused on the "What I would like to have is a setValue(AnyTag &tag, T &val) where T is the runtime type of the AnyTag." part of the request.

like image 178
sehe Avatar answered Nov 01 '22 00:11

sehe