40

C++11 のタプルは便利ですが、私には 2 つの大きな欠点があります。インデックスによるメンバーへのアクセスは

  1. 読めない
  2. 維持するのが難しい(タプルの途中に要素を追加すると、めちゃくちゃになる)

本質的に私が達成したいのはこれです

tagged_tuple <name, std::string, age, int, email, std::string> get_record (); {/*...*/}
// And then soomewhere else

std::cout << "Age: " << get_record().get <age> () << std::endl;

似たようなもの(型のタグ付け)がboost::property_mapに実装されていますが、任意の数の要素を持つタプルに実装する方法がわかりません

PSタプル要素インデックスで列挙型を定義することを提案しないでください。

UPD OK、これが動機です。私のプロジェクトでは、多くの異なるタプルを「オンザフライ」で定義できるようにする必要があり、それらすべてに特定の共通の関数と演算子が必要です。これは構造体では実現できません

UPD2 実際、私の例はおそらく実装が少し非現実的です。これはどう?

tagged_tuple <tag<name, std::string>, tag<age, int>, tag<email, std::string>> get_record (); {/*...*/}
// And then somewhere else

std::cout << "Age: " << get_record().get <age> () << std::endl;
4

8 に答える 8

54

これを行う既存のクラスは知りませんが、std::tupleと インデックス タイプリストを使用して何かを一緒に投げるのはかなり簡単です。

#include <tuple>
#include <iostream>

template<typename... Ts> struct typelist {
  template<typename T> using prepend = typelist<T, Ts...>;
};

template<typename T, typename... Ts> struct index;
template<typename T, typename... Ts> struct index<T, T, Ts...>:
  std::integral_constant<int, 0> {};
template<typename T, typename U, typename... Ts> struct index<T, U, Ts...>:
  std::integral_constant<int, index<T, Ts...>::value + 1> {};

template<int n, typename... Ts> struct nth_impl;
template<typename T, typename... Ts> struct nth_impl<0, T, Ts...> {
  using type = T; };
template<int n, typename T, typename... Ts> struct nth_impl<n, T, Ts...> {
  using type = typename nth_impl<n - 1, Ts...>::type; };
template<int n, typename... Ts> using nth = typename nth_impl<n, Ts...>::type;

template<int n, int m, typename... Ts> struct extract_impl;
template<int n, int m, typename T, typename... Ts>
struct extract_impl<n, m, T, Ts...>: extract_impl<n, m - 1, Ts...> {};
template<int n, typename T, typename... Ts>
struct extract_impl<n, 0, T, Ts...> { using types = typename
  extract_impl<n, n - 1, Ts...>::types::template prepend<T>; };
template<int n, int m> struct extract_impl<n, m> {
  using types = typelist<>; };
template<int n, int m, typename... Ts> using extract = typename
  extract_impl<n, m, Ts...>::types;

template<typename S, typename T> struct tt_impl;
template<typename... Ss, typename... Ts>
struct tt_impl<typelist<Ss...>, typelist<Ts...>>:
  public std::tuple<Ts...> {
  template<typename... Args> tt_impl(Args &&...args):
    std::tuple<Ts...>(std::forward<Args>(args)...) {}
  template<typename S> nth<index<S, Ss...>::value, Ts...> get() {
    return std::get<index<S, Ss...>::value>(*this); }
};
template<typename... Ts> struct tagged_tuple:
  tt_impl<extract<2, 0, Ts...>, extract<2, 1, Ts...>> {
  template<typename... Args> tagged_tuple(Args &&...args):
    tt_impl<extract<2, 0, Ts...>, extract<2, 1, Ts...>>(
      std::forward<Args>(args)...) {}
};

struct name {};
struct age {};
struct email {};

tagged_tuple<name, std::string, age, int, email, std::string> get_record() {
  return { "Bob", 32, "bob@bob.bob"};
}

int main() {
  std::cout << "Age: " << get_record().get<age>() << std::endl;
}

おそらく、既存のアクセサーの上にアクセサーを書きconst、右辺値を付けたいと思うでしょう。get

于 2012-10-25T09:58:55.307 に答える
10

C++ には、 ;structのように反復可能な型がありません。tupleそれは/またはのいずれかです。

それに最も近いのは、Boost.Fusion のstruct adapterを使用することです。これにより、構造体を Fusion シーケンスとして使用できます。もちろん、これも一連のマクロを使用しており、構造体のメンバーを反復処理する順序で明示的にリストする必要があります。ヘッダー内 (多くの翻訳単位で構造体を反復処理する場合)。

実際、私の例はおそらく実装が少し非現実的です。これはどう?

そのようなものを実装することもできますが、それらの識別子は実際には型または変数などである必要があります。

于 2012-10-25T09:52:45.297 に答える
2

ここで解決しなければならない実際の問題は次のとおりです。

  • タグは必須ですか、それともオプションですか?
  • タグはユニークですか?コンパイル時に強制されますか?
  • タグはどのスコープにありますか? あなたの例は、タイプにカプセル化されるのではなく、宣言スコープ内でタグを宣言しているように見えますが、これは最適ではない可能性があります。

ecatmurは良い解決策を提案しました。しかし、タグはカプセル化されておらず、タグ宣言はやや不器用です。C++14 では、タイプによるタプル アドレス指定が導入されます。これにより、設計が簡素化され、タグの一意性が保証されますが、スコープは解決されません。

Boost Fusion Mapも同様の用途に使用できますが、やはりタグの宣言は理想的ではありません。

c++ Standard Proposal forumには、名前をテンプレート パラメーターに直接関連付けることで構文を簡素化する、同様の提案があります。

このリンクには、これを実装するさまざまな方法 ( ecatmurのソリューションを含む) がリストされており、この構文のさまざまなユースケースが示されています。

于 2014-04-10T13:55:48.840 に答える
1

type_pairこれを行う別の方法は次のとおりです。型を定義するのは少し醜いですが、ペアをクラスで定義するため( のように) 、コンパイル時のエラーを防ぐのに役立ちますstd::map。次のステップは、コンパイル時にキー/名前が一意であることを確認するためのチェックを追加することです

使用法:

   using user_t = tagged_tuple<type_pair<struct name, std::string>, type_pair<struct age, int>>;
  // it's initialized the same way as a tuple created with the value types of the type pairs (so tuple<string, int> in this case)
  user_t user  { "chris", 21 };
  std::cout << "Name: " << get<name>(user) << std::endl;
  std::cout << "Age: " << get<age>(user) << std::endl;
 // you can still access properties via numeric indexes as if the class was defined as tuple<string, int>
  std::cout << "user[0] = " << get<0>(user) << std::endl;

可能な限り std::tuple と同じように get をメンバー関数にすることは避けましたが、クラスに簡単に追加できます。 ソースコードはこちら

于 2015-02-10T05:45:36.590 に答える
0

ブースト プリプロセッサを使用して「c++ 名前付きタプル」を実装しました。以下の使用例をご覧ください。タプルから派生することで、比較、印刷、ハッシュ、シリアル化を無料で取得できます (タプルに対して定義されていると仮定します)。

#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/comma_if.hpp>


#define CM_NAMED_TUPLE_ELEMS_ITR(r, xxx, index, x ) BOOST_PP_COMMA_IF(index) BOOST_PP_TUPLE_ELEM(2,0,x) 
#define CM_NAMED_TUPLE_ELEMS(seq) BOOST_PP_SEQ_FOR_EACH_I(CM_NAMED_TUPLE_ELEMS_ITR, "dum", seq)
#define CM_NAMED_TUPLE_PROPS_ITR(r, xxx, index, x) \
      BOOST_PP_TUPLE_ELEM(2,0,x) BOOST_PP_CAT(get_, BOOST_PP_TUPLE_ELEM(2,1,x))() const { return get<index>(*this); } \
      void BOOST_PP_CAT(set_, BOOST_PP_TUPLE_ELEM(2,1,x))(const BOOST_PP_TUPLE_ELEM(2,0,x)& oo) { get<index>(*this) = oo; }
#define CM_NAMED_TUPLE_PROPS(seq) BOOST_PP_SEQ_FOR_EACH_I(CM_NAMED_TUPLE_PROPS_ITR, "dum", seq)
#define cm_named_tuple(Cls, seq) struct Cls : tuple< CM_NAMED_TUPLE_ELEMS(seq)> { \
        typedef tuple<CM_NAMED_TUPLE_ELEMS(seq)> Base;                      \
        Cls() {}                                                            \
        template<class...Args> Cls(Args && ... args) : Base(args...) {}     \
        struct hash : std::hash<CM_NAMED_TUPLE_ELEMS(seq)> {};            \
        CM_NAMED_TUPLE_PROPS(seq)                                           \
        template<class Archive> void serialize(Archive & ar, arg const unsigned int version)() {                                                    \
          ar & boost::serialization::base_object<Base>(*this);                              \
        }                                                                   \
      }

//
// Example:
//
// class Sample {
//   public:
//   void do_tata() {
//     for (auto& dd : bar2_) {
//       cout << dd.get_from() << " " << dd.get_to() << dd.get_tata() << "\n";
//       dd.set_tata(dd.get_tata() * 5);
//     }
//     cout << bar1_ << bar2_ << "\n";
//   }
//
//   cm_named_tuple(Foo, ((int, from))((int, to))((double, tata)));  // Foo == tuple<int,int,double> with named get/set functions
//
//   unordered_set<Foo, Foo::hash> bar1_;
//   vector<Foo> bar2_;  
// };

上記のコード サンプルでは、​​vector/tuple/unordered_set に対して「一般的な」ostream 印刷関数が定義されていることを前提としていることに注意してください。

于 2013-06-12T18:00:20.367 に答える