struct to/from std::tuple conversion

后端 未结 4 1794
[愿得一人]
[愿得一人] 2021-02-05 13:30

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

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


        
相关标签:
4条回答
  • 2021-02-05 14:09

    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.

    0 讨论(0)
  • 2021-02-05 14:12

    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, e.g;

    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.

    0 讨论(0)
  • 2021-02-05 14:21

    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.

    0 讨论(0)
  • 2021-02-05 14:26

    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.

    0 讨论(0)
提交回复
热议问题