简体   繁体   中英

struct to/from std::tuple conversion

Assuming I have struct and std::tuple with same type layout:

struct MyStruct { int i; bool b; double d; }
using MyTuple = std::tuple<int,bool,double>;

Is there any standartized way to cast one to another?

PS I know that trivial memory copying can do the trick, but it is alignment and implementation dependent

We can use structured bindings to convert a struct into a tuple with a bit of work.

Struct-to-tuple is very awkward.

template<std::size_t N>
struct to_tuple_t;

template<>
struct to_tuple_t<3> {
  template<class S>
  auto operator()(S&& s)const {
    auto[e0,e1,e2]=std::forward<S>(s);
    return std::make_tuple(e0, e1, e2);
  }
};

Now, write a to_tuple_t for each size you want to support. This gets tedious. Sadly I know of no way to introduce a parameter pack there.

template<std::size_t N, class S>
auto to_tuple(S&& s) {
  return to_tuple_t<N>{}(std::forward<S>(s));
}

I know of no way to calculate the value of N required either. So you'd have to type the 3 in auto t = to_tuple<3>(my_struct); when you call it.

I am not a master of structured bindings. There is probably a && or & or a decltype that would permit perfect forwarding on these lines:

    auto[e0,e1,e2]=std::forward<S>(s);
    return std::make_tuple(e0, e1, e2);

but without a compiler to play with, I'll be conservative and make redundant copies.


Converting a tuple into a struct is easy:

template<class S, std::size_t...Is, class Tup>
S to_struct( std::index_sequence<Is...>, Tup&& tup ) {
  using std::get;
  return {get<Is>(std::forward<Tup>(tup))...};
}
template<class S, class Tup>
S to_struct( Tup&&tup ) {
  using T=std::remove_reference_t<Tup>;

  return to_struct(
    std::make_index_sequence<std::tuple_size<T>{}>{},
    std::forward<Tup>(tup)
  );
}

SFINAE support based off tuple_size might be good for to_struct .

The above code works with all tuple-likes, like std::pair , std::array , and anything you custom-code to support structured bindings ( tuple_size and get<I> ).


Amusingly,

std::array<int, 3> arr{1,2,3};
auto t = to_tuple<3>(arr);

works and returns a tuple with 3 elements, as to_tuple is based on structured bindings, which work with tuple-likes as input.

to_array is another possibility in this family.

Unfortunately there is no automatic way to do that, BUT an alternative is adapt the struct to Boost.Fusion sequence. You do this once and for all for each new class.

#include <boost/fusion/adapted/struct/adapt_struct.hpp>
...
struct MyStruct { int i; bool b; double d; }

BOOST_FUSION_ADAPT_STRUCT(
    MyStruct,
    (int, i)
    (bool, b)
    (double, d)
)

The use MyStruct as if it where a Fusion.Sequence (it fits generically almost everywhere you already use std::tuple<...> , if you make those functions generic.) As a bonus you will not need to copy your data members at all.

If you really need to convert to std::tuple , after "Fusion-adapting" you can do this:

#include <boost/fusion/adapted/std_tuple.hpp>
#include <boost/fusion/algorithm/iteration/for_each.hpp>
#include <boost/fusion/algorithm/transformation/zip.hpp>
...
auto to_tuple(MyStruct const& ms){
   std::tuple<int, bool, double> ret;
   auto z = zip(ret, ms);
   boost::fusion::for_each(z, [](auto& ze){get<0>(ze) = get<1>(ze);});
   // or use boost::fusion::copy
   return ret;
}

The truth is that std::tuple is a half-backed feature. It is like having STD containers and no algorithms. Fortunatelly we have #include <boost/fusion/adapted/std_tuple.hpp> that allows us to do amazing things.

Full code:

By including the std_tuple.hpp header from Boost.Fusion std::tuple is automatically adapted to a Boost.Fusion sequence, thus the following is possible by using Boost.Fusion as a bridge between your struct and std::tuple :

#include <iostream>
#include <string>
#include <tuple>

#include <boost/fusion/adapted/struct/adapt_struct.hpp>
#include <boost/fusion/algorithm/auxiliary/copy.hpp>
#include <boost/fusion/adapted/std_tuple.hpp>

struct foo
{
  std::string a, b, c;
  int d, e, f;
};

BOOST_FUSION_ADAPT_STRUCT(
    foo,
    (std::string, a)
    (std::string, b)
    (std::string, c)
    (int, d)
    (int, e)
    (int, f)
)

template<std::size_t...Is, class Tup>
foo to_foo_aux(std::index_sequence<Is...>, Tup&& tup) {
  using std::get;
  return {get<Is>(std::forward<Tup>(tup))...};
}
template<class Tup>
foo to_foo(Tup&& tup) {
  using T=std::remove_reference_t<Tup>;
  return to_foo_aux(
    std::make_index_sequence<std::tuple_size<T>{}>{},
    std::forward<Tup>(tup)
  );
}

template<std::size_t...Is>
auto to_tuple_aux( std::index_sequence<Is...>, foo const& f ) {
  using boost::fusion::at_c;
  return std::make_tuple(at_c<Is>(f)...);
}
auto to_tuple(foo const& f){
  using T=std::remove_reference_t<foo>;
  return to_tuple_aux(
    std::make_index_sequence<boost::fusion::result_of::size<foo>::type::value>{},
    f
  );    
}

int main(){


    foo f{ "Hello", "World", "!", 1, 2, 3 };

    std::tuple<std::string, std::string, std::string, int, int, int> dest = to_tuple(f);
    // boost::fusion::copy(f, dest); // also valid  but less general than constructor

    std::cout << std::get<0>(dest) << ' ' << std::get<1>(dest) << std::get<2>(dest) << std::endl;
    std::cout << at_c<0>(dest) << ' ' << at_c<1>(dest) << at_c<2>(dest) << std::endl; // same as above

    foo f2 = to_foo(dest);

    std::cout << at_c<0>(f2) << ' ' << at_c<1>(f2) << at_c<2>(f2) << std::endl;
}

I will not recommend reinterpret_cast<std::tuple<...>&>(mystructinstance.i) because that will result in negative votes and it is not portable.

Is there any standartized way to cast one to another?

There is no way to "cast" the one to the other.

The easiest may be to use a std::tie to pack the tuple out into the struct ;

struct MyStruct { int i; bool b; double d; };
using MyTuple = std::tuple<int,bool,double>;

auto t = std::make_tuple(42, true, 5.1);
MyStruct s;

std::tie(s.i, s.b, s.d) = t;

Demo .

You can further wrap this up in higher level macros or "generator" ( make style) functions, eg;

std::tuple<int, bool, double> from_struct(MyStruct const& src)
{
  return std::make_tuple(src.i, src.b, src.d);
}

MyStruct to_struct(std::tuple<int, bool, double> const& src)
{
  MyStruct s;
  std::tie(s.i, s.b, s.d) = src;
  return s;
}

I know that trivial memory copying can do the trick, but it is alignment and implementation dependent?

You mention the "trivial memory copy" would work - only for copying the individual members. So basically, a memcpy of the entire structure to the tuple and vice-versa is not going to always behave as you expect (if ever); the memory layout of a tuple is not standardised. If it does work, it is highly dependent on the implementation.

Tuple to struct conversion is trivial, but backward I think is impossible at current C++ level in general.

#include <type_traits>
#include <utility>

#include <tuple>

namespace details
{

template< typename result_type, typename ...types, std::size_t ...indices >
result_type
make_struct(std::tuple< types... > t, std::index_sequence< indices... >) // &, &&, const && etc.
{
    return {std::get< indices >(t)...};
}

}

template< typename result_type, typename ...types >
result_type
make_struct(std::tuple< types... > t) // &, &&, const && etc.
{
    return details::make_struct< result_type, types... >(t, std::index_sequence_for< types... >{}); // if there is repeated types, then the change for using std::index_sequence_for is trivial
}

#include <cassert>
#include <cstdlib>

int main()
{
    using S = struct { int a; char b; double c; };
    auto s = make_struct< S >(std::make_tuple(1, '2', 3.0));
    assert(s.a == 1);
    assert(s.b == '2');
    assert(s.c == 3.0);
    return EXIT_SUCCESS;
}

Live example .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM