簡體   English   中英

struct to / from std :: tuple conversion

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

假設我有相同類型布局的structstd::tuple

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

是否有任何標准化的方式來相互投射?

PS我知道瑣碎的內存復制可以解決這個問題,但它依賴於對齊和實現

我們可以使用結構化綁定將結構轉換為具有一些功能的元組。

結構到元組非常尷尬。

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);
  }
};

現在,為要支持的每個to_tuple_t一個to_tuple_t 這很乏味。 可悲的是,我知道無法在那里引入參數包。

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

我知道無法計算N所需的值。 這樣你就必須鍵入3auto t = to_tuple<3>(my_struct); 當你打電話給它。

我不是結構化綁定的大師。 可能有一個&&&或decltype可以在這些行上完美轉發:

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

但是如果沒有編譯器可以使用,我會保守並制作冗余副本。


將元組轉換為結構很容易:

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)
  );
}

基於tuple_size SFINAE支持可能對to_struct

上面的代碼適用於所有元組,如std::pairstd::array ,以及任何自定義代碼以支持結構化綁定( tuple_sizeget<I> )的代碼。


有趣的是,

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

工作並返回一個包含3個元素的元組,因為to_tuple基於結構化綁定,它與元組to_tuple作為輸入。

to_array是這個家庭的另一種可能性。

不幸的是,沒有自動的方法可以做到這一點,但另一種方法是將結構調整為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)
)

使用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;
}

事實上, std::tuple是一個半支持的功能。 這就像擁有STD容器而沒有算法。 幸運的是,我們有#include <boost/fusion/adapted/std_tuple.hpp> ,它可以讓我們做出驚人的事情。

完整代碼:

通過包含來自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;
}

建議使用reinterpret_cast<std::tuple<...>&>(mystructinstance.i)因為這將導致負面投票並且不可移植。

是否有任何標准化的方式來相互投射?

沒有辦法將這一個“施放”到另一個。

最簡單的方法是使用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;

演示

您可以進一步將其包含在更高級別的宏或“生成器”( 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;
}

我知道瑣碎的內存復制可以做到這一點,但它是對齊和實現依賴?

你提到“瑣碎的記憶副本”會起作用 - 僅用於復制個別成員。 所以基本上,整個結構到tuplememcpy反之亦然,並不總是像你期望的那樣(如果有的話); tuple的內存布局不是標准化的。 如果確實有效,則高度依賴於實施。

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;
}

實例

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM