web-dev-qa-db-ja.com

std :: Tuple変換との間の構造体

同じタイプのレイアウトのstructstd::Tupleがあると仮定します。

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

相互にキャストするための標準的な方法はありますか?

P.S.些細なメモリコピーでうまくいくことは知っていますが、アライメントと実装に依存します

13
Andrei R.

残念ながら、それを自動的に行う方法はありませんが、代わりに構造体を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)
)
_

Fusion.Sequenceの場合と同じようにMyStructを使用します(これらの関数を汎用化すると、すでに_std::Tuple<...>_を使用しているほとんどすべての場所に汎用的に適合します)。ボーナスとして、データをコピーする必要はありません。すべてのメンバー。

本当に_std::Tuple_に変換する必要がある場合は、「Fusion-adapting」の後でこれを行うことができます。

_#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を構造体と_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 しませんreinterpret_cast<std::Tuple<...>&>(mystructinstance.i)をお勧めします。これは、反対票が投じられ、移植性がないためです。

7
alfC

構造化バインディングを使用して、少しの作業で構造体をタプルに変換できます。

Struct-to-Tupleは非常に厄介です。

_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_を記述します。これは面倒になります。悲しいことに、そこにパラメータパックを導入する方法がわかりません。

_template<std::size_t N, class S>
auto to_Tuple(S&& s) {
  return to_Tuple_t<N>{}(std::forward<S>(s));
}
_

必要なNの値を計算する方法もわかりません。したがって、呼び出すときにauto t = to_Tuple<3>(my_struct);に_3_を入力する必要があります。

私は構造化バインディングのマスターではありません。おそらく、_&&_または_&_、あるいはこれらの行で完全な転送を可能にする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::pair_、_std::array_、および構造化バインディング(_Tuple_size_および_get<I>_)をサポートするためにカスタムコーディングしたものなど、すべてのタプルのようなもので機能します。


面白いことに、

_std::array<int, 3> arr{1,2,3};
auto t = to_Tuple<3>(arr);
_

_to_Tuple_は構造化されたバインディングに基づいているため、3つの要素を持つタプルが機能して返されます。これは、入力としてタプルのようなもので機能します。

_to_array_は、このファミリのもう1つの可能性です。

相互にキャストするための標準的な方法はありますか?

一方を他方に「キャスト」する方法はありません。

最も簡単なのは 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スタイル)関数でまとめることができます。

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

些細なメモリコピーでうまくいくことは知っていますが、アライメントと実装に依存していますか?

「簡単なメモリコピー」が機能するとおっしゃいましたが、個々のメンバーをコピーする場合のみです。したがって、基本的に、構造全体のmemcpyからTupleへの、またはその逆は、常に期待どおりに動作するとは限りません(ある場合)。 Tupleのメモリレイアウトは標準化されていません。それが機能する場合、それは実装に大きく依存します。

3
Niall

タプルから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;
}

実例

1
Orient