简体   繁体   English

struct to / from std :: tuple conversion

[英]struct to/from std::tuple conversion

Assuming I have struct and std::tuple with same type layout: 假设我有相同类型布局的structstd::tuple

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 PS我知道琐碎的内存复制可以解决这个问题,但它依赖于对齐和实现

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. 现在,为要支持的每个to_tuple_t一个to_tuple_t 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. 我知道无法计算N所需的值。 So you'd have to type the 3 in auto t = to_tuple<3>(my_struct); 这样你就必须键入3auto 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: 可能有一个&&&或decltype可以在这些行上完美转发:

    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 . 基于tuple_size SFINAE支持可能对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> ). 上面的代码适用于所有元组,如std::pairstd::array ,以及任何自定义代码以支持结构化绑定( tuple_sizeget<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. 工作并返回一个包含3个元素的元组,因为to_tuple基于结构化绑定,它与元组to_tuple作为输入。

to_array is another possibility in this family. to_array是这个家庭的另一种可能性。

Unfortunately there is no automatic way to do that, BUT an alternative is adapt the struct to Boost.Fusion sequence. 不幸的是,没有自动的方法可以做到这一点,但另一种方法是将结构调整为Boost.Fusion序列。 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. 使用MyStruct就好像它在Fusion.Sequence的位置(它几乎适用于你已经使用std::tuple<...> ,如果你使这些功能通用。)作为奖励你不需要复制你的数据成员一点都不

If you really need to convert to std::tuple , after "Fusion-adapting" you can do this: 如果你真的需要转换为std::tuple ,在“Fusion-adaptting”之后你可以这样做:

#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. 事实上, std::tuple是一个半支持的功能。 It is like having STD containers and no algorithms. 这就像拥有STD容器而没有算法。 Fortunatelly we have #include <boost/fusion/adapted/std_tuple.hpp> that allows us to do amazing things. 幸运的是,我们有#include <boost/fusion/adapted/std_tuple.hpp> ,它可以让我们做出惊人的事情。

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 : 通过包含来自Boost.Fusion的std_tuple.hpp头, std::tuple会自动适应Boost.Fusion序列,因此可以通过使用Boost.Fusion作为struct和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. 建议使用reinterpret_cast<std::tuple<...>&>(mystructinstance.i)因为这将导致负面投票并且不可移植。

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 ; 最简单的方法是使用std::tie将元组打包到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; 您可以进一步将其包含在更高级别的宏或“生成器”( make style)函数中,例如;

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); 所以基本上,整个结构到tuplememcpy反之亦然,并不总是像你期望的那样(如果有的话); the memory layout of a tuple is not standardised. tuple的内存布局不是标准化的。 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. struct转换的元组是微不足道的,但是我认为在目前的C ++级别上是不可能的。

#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 . 实例

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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