1

I have a template method that uses boost::get of boost:variant module:

typedef boost::variant<int, std::string, bool, uint8_t> Variant;

template <class T>
void write(const Variant& t) {
    size_t sizeT = boost::apply_visitor(SizeOfVisitor(), t);
    memcpy(&v[offset], &boost::get<T>(t), sizeT);
}

The problem is I know the underlying type of Variant only at runtime. And AFAIK I can query for it only with which() method.

Is there any way I can pass the type T, which is the underlying type of Variant to this method? For instance, using which() I do know, what type there is, but how to pass it?

switch (m_var.which()) { // Returns an int index of types in the order of passed template classes
    case 0: // This is int
    case 1: // This is std::string
    ...
}
...
Writer.write<???>(m_var); // How to pass the type here?

EDIT: If you know any other way to achieve the desired result - actually getting the address of boost::variant inner variable, to copy from there, please share with me your ideas

Thank you

rightaway717
  • 2,631
  • 3
  • 29
  • 43
  • 1
    You cannot `memcpy` an `std::string` value. – Luc Danton Dec 17 '14 at 13:31
  • I didn't actually memcpy it. For string I had a separate method to memcpy its content (mystring.c_str()). I think this is allowed, right? – rightaway717 Dec 17 '14 at 14:10
  • The way the code in your question is set up, you *will* write a function that (incorrectly) `memcpy`s an `std::string` should it receive an `std::string`-holding variant. – Luc Danton Dec 17 '14 at 14:37

2 Answers2

1

I happen to have written a very similar answer here:

Again, the most important would be that it's completely bogus to use memcpy with non-POD datatypes (so you cannot use it with std::string. Ever).

The way to operate on variants with the type known only at runtime is using boost::static_visitor<>.

Here's the example with main() adapted to be close to what you wanted to achieve, apparently,

Live On Coliru

#include <boost/variant.hpp>
#include <boost/bind.hpp>

#include <boost/array.hpp> // just as a sample
#include <iostream>

namespace serialization {

    namespace customization {
        template<typename T, typename Out, typename R = typename boost::enable_if<boost::is_pod<T>, void>::type>
            void do_serialize(T const& x, Out& out)
            {
                static_assert(boost::is_pod<T>(), "");
                char const* rawp = reinterpret_cast<char const*>(&x);
                std::copy(rawp, rawp+sizeof(T), out);
            }

        template<typename Out>
            void do_serialize(std::string const& x, Out& out)
            {
                do_serialize(x.size(), out);
                for(auto ch : x)
                    do_serialize(ch, out);
            }
    }

    struct serialize_f : public boost::static_visitor<> {
        template<typename Out, typename... T>
            void operator()(boost::variant<T...> const& v, Out& out) const
            {
                boost::apply_visitor(boost::bind(*this, _1, boost::ref(out)), v);
            }

        template<typename T, typename Out>
            void operator()(T const& x, Out& out) const
            {
                using customization::do_serialize; // ADL dispatch
                do_serialize(x, out);
            }
    };

    template <typename T, typename Out>
        Out serialize(T const& v, Out out) {
            const static serialize_f _vis {};
            _vis(v, out);
            return out;
        }

}

namespace MyUserTypes {

    struct A {
        std::string name;
        int i;
    };

    template<typename Out> void do_serialize(A const& v, Out& out) { // ADL will find this
        serialization::serialize(v.name, out);
        serialization::serialize(v.i, out);
    }
}

int main() {
    using namespace serialization;
    std::vector<uint8_t> binary_data;
    auto out_inserter = back_inserter(binary_data);

    // variants and custom types
    typedef boost::variant<MyUserTypes::A, boost::array<char, 42> > V;
    MyUserTypes::A myA { "0123456789", 99 };
    V v = boost::array<char,42>();

    serialize(myA, out_inserter);
    serialize(v, out_inserter);
    v = myA;
    serialize(v, out_inserter);

    std::cout << "Bytes in binary_data vector: " << binary_data.size() << "\n";
}
Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Could you please explain it how does `boost::apply_visitor(boost::bind(*this, _1, boost::ref(out)), v);` works, that `operator ()` for visitor class is called with T=concrete type (int, float etc). I don't understand it how _1 substitution works here. There is no substitution variable after `bind` function. I checked boost manual, though this part is not clear in docs too. – rightaway717 Dec 29 '14 at 14:34
  • @rightaway717 The `apply_visitor` expects [a unary functor (in this case)](http://www.boost.org/doc/libs/1_57_0/doc/html/boost/apply_visitor.html). It calls that functor with the concrete element value contained in the variant. Now, because our function expects two arguments, we bind the second to `ref(out)` so a unary bound functor remains that is compatible with `apply_visitor`. – sehe Dec 29 '14 at 14:55
0
switch (m_var.which()) { // Returns an int index of types in the order of passed template classes
    case 0: // This is int
      Writer.write<int>(m_var);
    break;
    case 1: // This is std::string
      Writer.write<std::string>(m_var);
    break;
    ...
}

:)

Drax
  • 12,682
  • 7
  • 45
  • 85
  • Haha :) That's funny. But this is not the place where I want to write things. I'm writing stuff in another method. – rightaway717 Dec 17 '14 at 11:37
  • @rightaway717 The solution is the same, juste move the switch at the place where you want to do your dispatch. You'll need a runtime dispatching method in all cases, and the only improvement to this is to have some kind of map thats maps a value to a type, like a `boost::fusion::map` but the other way, i'm not sure that's even doable in theory so i'm afraid this is the best you will get :) – Drax Dec 17 '14 at 12:05
  • If you're going to advocate switching on magic type indices, why not advocate some goto at the same time – sehe Dec 17 '14 at 13:09
  • @sehe Well this is the best that comes to my mind, a visitor based solution would probably be better indeed but also implies a lot more boilerplate code ^^ – Drax Dec 17 '14 at 13:22
  • @Drax the main reason you see "boiler plate" in my answer is because my answer does a lot more (it adds actual serialization to generic output iterators and customization points (`do_serialize`) for custom types). You can simplify if you are willing to stay with the scope of your answer: http://paste.ubuntu.com/9640554/. At least then you don't hard code the type index/type list (which is a nice source of errors and maintenance costs). And it's in just as many lines of code, but without `...` left out lines :) – sehe Dec 29 '14 at 15:02
  • @sehe Fair enough you convinced me :) I still find it a little too bad that you have to create a new class, it would be great if we could pass a generic lambda =/ – Drax Dec 29 '14 at 18:03
  • 1
    @Drax I'm pretty sure that's entirely possible if they restrict the use to c++14 and up. In fact, you can already do the plumbing and get "polymorphic" inline visitors defined with just c++11: see [**`make_visitor`** in this old answer of mine](http://stackoverflow.com/a/17776618/85371). The plain code already uses a local struct defined in a lambda - reducing namespace clutter. There's a related variation **[here](http://stackoverflow.com/a/18432618/85371)**. There's still some ironing to do if the result types don't match up. – sehe Dec 29 '14 at 20:17