struct to/from std :: tuple conversion

And*_* R. 11 c++ c++17

假设我有structstd::tuple同类型的布局:

struct MyStruct { int i; bool b; double d; }
using MyTuple = std::tuple<int,bool,double>;
Run Code Online (Sandbox Code Playgroud)

是否有任何标准化的方式来相互投射?

PS我知道琐碎的内存复制可以解决这个问题,但它依赖于对齐和实现

Yak*_*ont 10

我们可以使用结构化绑定将结构转换为具有一些功能的元组.

结构到元组非常尴尬.

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);
  }
};
Run Code Online (Sandbox Code Playgroud)

现在,to_tuple_t为每个要支持的大小写一个.这很乏味.可悲的是,我知道无法在那里引入参数包.

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

我知道无法计算N所需的值.所以,你必须输入3auto t = to_tuple<3>(my_struct);当你调用它.

我不是结构化绑定的大师.可能有一个&&&一个decltype允许在这些行上完美转发:

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

但是如果没有编译器可以使用,我会保守并制作冗余副本.


将元组转换为结构很容易:

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)
  );
}
Run Code Online (Sandbox Code Playgroud)

基于SFINAE支持tuple_size可能会有所帮助to_struct.

上面的代码适用于所有元组,例如std::pair,std::array以及您自定义代码以支持结构化绑定(tuple_sizeget<I>)的任何内容.


有趣的是,

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

工作并返回一个包含3个元素的元组,这to_tuple是基于结构化绑定,它与元组喜欢作为输入.

to_array 是这个家庭的另一种可能性.


alf*_*lfC 7

不幸的是,没有自动的方法可以做到这一点,但另一种方法是将结构调整为Boost.Fusion序列.您可以为每个新课程一劳永逸地完成这项工作.

#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)
)
Run Code Online (Sandbox Code Playgroud)

使用MyStruct仿佛它放在一个Fusion.Sequence(它符合一般几乎无处不在,你已经在使用std::tuple<...>,如果你把这些功能一般.)当你将不再需要你的数据成员在所有复制的奖金.

如果你真的需要转换为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;
}
Run Code Online (Sandbox Code Playgroud)

事实是,这std::tuple是一个半支持的功能.这就像拥有STD容器而没有算法.幸运的是,我们可以#include <boost/fusion/adapted/std_tuple.hpp>让我们做出惊人的事情.

完整代码:

通过包含std_tuple.hpp来自Boost.Fusion 的头文件std::tuple自动适应Boost.Fusion序列,因此可以通过使用Boost.Fusion作为结构和之间的桥梁来实现以下功能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;
}
Run Code Online (Sandbox Code Playgroud)

不会建议,reinterpret_cast<std::tuple<...>&>(mystructinstance.i)因为这将导致负面投票而且不可移植.

  • "*我__将不会___推荐`reinterpret_cast <std :: tuple <...>&>(mystructinstance.i)`因为这将导致负面投票并且不可移植.*"你不应该推荐它,因为它是彻头彻尾的_illegal_,不仅仅是因为你会被投票.; - ] +1虽然提到Fusion! (4认同)
  • 如果包含 boost fusion std_tuple 标头,它会调整“std::tuple”,以便您只需调用“boost::fusion::copy()”即可从调整后的结构复制到“std::tuple”。 .你不需要使用你的自定义迭代..实际上,它将 `std::tuple` 提升为融合序列,因此任一方向的复制都应该是可能的(并且它足够智能来处理重复的类型..) (2认同)
  • 我冒昧地添加了复制示例,我认为这使这个答案变得完整 - 如果您觉得它不相关,请将其删除.. (2认同)

Nia*_*all 6

有没有一种标准化的方法可以将一个角色投射到另一个角色上?

没有办法将一个“投射”到另一个。

最简单的可能是使用 astd::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;
Run Code Online (Sandbox Code Playgroud)

演示

您可以进一步将其包装在更高级别的宏或“生成器”(make样式)函数中,例如;

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;
}
Run Code Online (Sandbox Code Playgroud)

我知道简单的内存复制可以做到这一点,但它依赖于对齐和实现?

您提到“琐碎内存复制”可以工作 - 仅用于复制单个成员。所以基本上,memcpy整个结构的 atuple和反之亦然不会总是像你期望的那样(如果有的话);a 的内存布局tuple没有标准化。如果它确实有效,则高度依赖于实施。


Ori*_*ent 5

元组到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;
}
Run Code Online (Sandbox Code Playgroud)

活生生的例子