10

I want to construct boost::variants containing default-constructed values, specified with a type index - without writing my own switch statement over the type index.

I figure this must be possible, somehow, with MPL?

To clarify though, the index is not a compile-time constant expression.

The use case is that I need to construct a variant which will later be replaced with one containing the correct value, but at this point I only know the type index. Think of it as a lazy deserialisation problem.

James
  • 24,676
  • 13
  • 84
  • 130
  • Since you mention MPL, I would think that `N` (the type index) is known at compile-time, however lazy deserialization suggests it might only be available at runtime --> which is it ? – Matthieu M. Feb 16 '12 at 14:40
  • @MatthieuM. This would be impossible with the later. I hope for the first, otherwise my answer is worthless as well. – pmr Feb 16 '12 at 14:43
  • @MatthieuM. Ah, good point: it is only available at run-time. – James Feb 16 '12 at 14:43
  • @Autopulated Did this win you the 'weirdest code checked in this month'-award or why the bounty? – pmr Feb 25 '12 at 16:01
  • @pmr, in fact I ended up avoiding creating the empty variants in the first place, which was probably the sensible thing to do - I appreciate the effort you put into solving my crazy problem though! – James Feb 25 '12 at 16:25

1 Answers1

13

You need to use the variant::types typedef. This gives you an MPL compatible sequence which we can then use with mpl::at and a template to do our bidding. This does the trick:

#include <string>
#include <boost/variant.hpp>
#include <boost/mpl/at.hpp>
#include <boost/mpl/int.hpp>

template<typename U, typename V>
void construct_in(V& v) {
  v = U();
  // modern
  // v = U{};
}

int main()
{
  typedef boost::variant<int, std::string> variant;
  typedef boost::mpl::at<variant::types, boost::mpl::int_<1>>::type pos;
  variant v;
  // use type deduction
  construct_in<pos>(v);
  // does not throw, does work
  std::string& s =boost::get<std::string>(v);
  return 0;
}

Here goes the runtime-variant:

#include <string>
#include <vector>
#include <functional>

#include <boost/variant.hpp>
#include <boost/mpl/at.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/for_each.hpp>

typedef boost::variant<int, std::string> variant;
typedef variant::types types;
typedef std::vector< std::function<void(variant&)> > fvec;

template<typename U, typename V>
void construct_in(V& v) {
  v = U{};
}

struct build_and_add {
  fvec* funcs;
  template<typename T>
  void operator()(T) {
    funcs->push_back(&construct_in<T, variant>);
  }
};


int main()
{

  variant v;
  std::vector< std::function<void(variant&)> > funcs;

  // cannot use a lambda, would need to be polymorphic
  build_and_add f = {&funcs};
  boost::mpl::for_each<types>(f);

  // this is runtime!
  int i = 1;

  funcs[i](v);
  // does not throw, does work
  std::string& s =boost::get<std::string>(v);
  return 0;
}

It's a little arcane and will need some tweaking with variadic arguments to be truly generic, but it does what you want. Someone else needs to figure out if this results in significant code blow-up.

pmr
  • 58,701
  • 10
  • 113
  • 156
  • Sorry for the clarification about the type-index being run-time, but I don't think this answer is useless - it would be possible to extend this to switch between N constant indices at run-time, where only N is fixed at compile time (and can even be extracted from the variant, perhaps?) – James Feb 16 '12 at 14:46
  • @Autopulated Not really, but some black magic might help. Think about the template we use for construction. We can generate all those templates at compile-time and store them as `std::function` objects in a vector and use this vector at runtime to look-up what we want to construct. I'll try to throw something together, but I don't know how good that would be in terms of code blow-up etc. – pmr Feb 16 '12 at 14:49
  • @pmr: my knee-jerk reaction was to use recursivity, but a vector (or array since N is constant) of pointer to functions seems to give a much better performance guarantee (no reliance on an eventual tail-recursion). You may need recursion for the initialization of the vector still, but it's a one off thing. – Matthieu M. Feb 16 '12 at 14:54
  • @MatthieuM. I'm a huge fan of `mpl::for_each` for such things. Writing recursive template functions with the MPL is always a bit tricky as their sequences are no real variadic templates. – pmr Feb 16 '12 at 15:00
  • @pmr: is it guaranteed that `mpl::for_each` will iterate the elements in order ? (and why is `build_and_add` initialized with a pointer and not a reference ?) – Matthieu M. Feb 16 '12 at 15:28
  • 1
    @MatthieuM. I prefer pointers of references for ad-hoc functors, takes away the need for constructors. About the order: I cannot find anything explicit, but `for_each` requires at most a `ForwardSequence` so it should not be allowed to do anything else. – pmr Feb 16 '12 at 15:36