Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conversion between std::tuple and boost::tuple

Given a boost::tuple and std::tuple, how do you convert between them?

In other words, how would you implement the following two functions?

template <typename... T> boost::tuple<T...> asBoostTuple(  std::tuple<T...> stdTuple);
template <typename... T>   std::tuple<T...> asStdTuple  (boost::tuple<T...> boostTuple);

Seems to be a straightforward thing, but I could not find any good solutions.


What did I try?

I ended up solving it with template programming. It seems to work in my concrete setup, but it does not feel right (too verbose), and I'm even not sure if it actually works in all situations (I'll come to that point later).

Anyway, here is the interesting part:

template<int offset, typename... T>
struct CopyStdTupleHelper
{
    static void copy(boost::tuple<T...> source, std::tuple<T...>& target) {
        std::get<offset>(target) = std::move(boost::get<offset>(source));
        CopyStdTupleHelper<offset - 1, T...>::copy(source, target);
    }

    static void copy(std::tuple<T...> source, boost::tuple<T...>& target) {
        boost::get<offset>(target) = std::move(std::get<offset>(source));
        CopyStdTupleHelper<offset - 1, T...>::copy(source, target);
    }
};

template<typename... T>
struct CopyStdTupleHelper<-1, T...>
{
    static void copy(boost::tuple<T...> source, std::tuple<T...>& target)
      { /* nothing to do (end of recursion) */ }

    static void copy(std::tuple<T...> source, boost::tuple<T...>& target)
      { /* nothing to do (end of recursion) */ }
};


std::tuple<T...> asStdTuple(boost::tuple<T...> boostTuple) {
    std::tuple<T...> result;
    CopyStdTupleHelper<sizeof...(T) - 1, T...>::copy(std::move(boostTuple), result);
    return result;
}

boost::tuple<T...> asBoostTuple(std::tuple<T...> stdTuple) {
    boost::tuple<T...> result;
    CopyStdTupleHelper<sizeof...(T) - 1, T...>::copy(std::move(stdTuple), result);
    return result;
}

I wonder if there is a more elegant way. It seems like a very common operation to wrap existing APIs using boost::tuple to std::tuple.


I tried to provide you with a minimal test example, where only the implementation of asBoostTuple and asStdTuple is missing. However, for some magic with boost::tuples::null_type, which I don't fully understand, it fails to compile. That is also the reason why I'm not sure that my existing solution can be generally applied.

Here is the snippet:

#include <tuple>
#include <boost/tuple/tuple.hpp>

template <typename... T>
boost::tuple<T...> asBoostTuple(std::tuple<T...> stdTuple) {
  boost::tuple<T...> result;
  // TODO: ...
  return result;
}

template <typename... T>
std::tuple<T...> asStdTuple(boost::tuple<T...> boostTuple) {
  std::tuple<T...> result;
  // TODO: ...
  return result;
}

int main() {
  boost::tuple<std::string, int, char> x = asBoostTuple(std::make_tuple("A", 1, 'x'));

  // ERROR: 
  std::tuple<std::string, int, char> y = asStdTuple<std::string, int, char>(x);

  return x == y;
}

The error message is:

example.cpp:20:38: error: no viable conversion from 'tuple<[3 *
      ...], boost::tuples::null_type, boost::tuples::null_type,
      boost::tuples::null_type, boost::tuples::null_type,
      boost::tuples::null_type, boost::tuples::null_type,
      boost::tuples::null_type>' to 'tuple<[3 * ...], (no
      argument), (no argument), (no argument), (no argument),
      (no argument), (no argument), (no argument)>'
  ...int, char> y = asStdTuple<std::string, int, char>(x);

I understand that Boost's implementation is not based on variadic templates, which should explain the null_type, but still I'm not sure how to avoid that compile error.

like image 452
Philipp Claßen Avatar asked Mar 24 '15 02:03

Philipp Claßen


1 Answers

The usual trick when doing this sort of manipulations is to construct an integer sequence and then use a pack expansion to initialize the new tuple.

The extra twist in this case is null_type. For that it's probably simplest to treat the tuple type as opaque, and manipulate it using boost::tuples::length and boost::tuples::element, which already handles null_type properly. This way you do not rely on the implementation details of boost tuples.

So:

template <typename StdTuple, std::size_t... Is>
auto asBoostTuple(StdTuple&& stdTuple, std::index_sequence<Is...>) {
    return boost::tuple<std::tuple_element_t<Is, std::decay_t<StdTuple>>...>
                        (std::get<Is>(std::forward<StdTuple>(stdTuple))...);
}

template <typename BoostTuple, std::size_t... Is>
auto asStdTuple(BoostTuple&& boostTuple, std::index_sequence<Is...>) {
    return std::tuple<typename boost::tuples::element<Is, std::decay_t<BoostTuple>>::type...>
                     (boost::get<Is>(std::forward<BoostTuple>(boostTuple))...);
}

template <typename StdTuple>
auto asBoostTuple(StdTuple&& stdTuple) {
  return asBoostTuple(std::forward<StdTuple>(stdTuple),
             std::make_index_sequence<std::tuple_size<std::decay_t<StdTuple>>::value>());
}

template <typename BoostTuple>
auto asStdTuple(BoostTuple&& boostTuple) {
  return asStdTuple(std::forward<BoostTuple>(boostTuple),
             std::make_index_sequence<boost::tuples::length<std::decay_t<BoostTuple>>::value>());
}

Demo.

Note that the basic structure of the code is exactly identical for the two situations: we obtain the size of the tuple (via boost::tuples::length or std::tuple_size), construct an integer sequence using std::make_index_sequence, and then use the integer sequence to obtain the types with boost::tuples::element and std::tuple_element, and the values with boost::get/std::get.

like image 192
T.C. Avatar answered Nov 10 '22 15:11

T.C.