web-dev-qa-db-ja.com

C ++ 11タグ付きタプル

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してくださいnot Tuple要素のインデックスで列挙型を定義することを提案します。

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

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

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;
33
user1773602

私はこれを行う既存のクラスを知りませんが、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, "[email protected]"};
}

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

おそらく、既存のものの上にconstおよびrvalue getアクセサーを記述したいと思うでしょう。

45
ecatmur

C++にはstruct型がなく、Tupleのように反復可能です。どちらかまたは両方です。

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

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

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

9
Nicol Bolas

私は誇示する独自の実装を持っていますが、ファイルの上に属性を宣言しないようにすることができます。宣言された属性を持つバージョンも存在しますが、それらを定義する必要はなく、宣言で十分です。

もちろん、これは純粋なSTLであり、プリプロセッサは使用しません。

例:

#include <named_tuples/Tuple.hpp>
#include <string>
#include <iostream>
#include <vector>

namespace {
unsigned constexpr operator "" _h(const char* c,size_t) { return named_tuples::const_hash(c); }
template <unsigned Id> using at = named_tuples::attribute_init_int_placeholder<Id>;
using named_tuples::make_Tuple;
}

int main() {
  auto test = make_Tuple( 
      at<"nom"_h>() = std::string("Roger")
      , at<"age"_h>() = 47
      , at<"taille"_h>() = 1.92
      , at<"liste"_h>() = std::vector<int>({1,2,3})
      );

  std::cout 
    << test.at<"nom"_h>() << "\n"
    << test.at<"age"_h>() << "\n"
    << test.at<"taille"_h>() << "\n"
    << test.at<"liste"_h>().size() << std::endl;

  test.at<"nom"_h>() = "Marcel";
  ++test.get<1>();

  std::cout 
    << test.get<0>() << "\n"
    << test.get<1>() << "\n"
    << test.get<2>() << "\n"
    << test.get<3>().size() << std::endl;

  return 0;
}

ここで完全なソースを見つけてください https://github.com/duckie/named_Tuple 。自由に読んでください、それは非常に簡単です。

7

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

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

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

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

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

このリンク はこれを実装するさまざまな方法をリストし( ecatmur のソリューションを含む)、この構文のさまざまな使用例を示します。

1
Thibaut

量産コードで同様の問題を「解決」しました。まず、通常の構造体(実際にはさまざまなメンバー関数を持つクラスですが、ここで関心のあるのはデータメンバーのみです)です...

_class Record
{
    std::string name;
    int age;
    std::string email;
    MYLIB_ENABLE_Tuple(Record) // macro
};
_

次に、構造体定義のすぐ下ですが、名前空間の外に、別のマクロがあります。

_MYLIB_DECLARE_Tuple(Record, (o.name, o.age, o.email))
_

このアプローチの欠点は、メンバー名を2回リストする必要があることですが、構造体のメンバー関数内で従来のメンバーアクセス構文を許可しながら、これが私が思いついた最高の方法です。マクロはデータメンバー自体の定義の非常に近くに表示されるため、それらを互いに同期させることはそれほど難しくありません。

別のヘッダーファイルにクラステンプレートがあります。

_template <class T>
class TupleConverter;
_

最初のマクロは、このテンプレートを構造体のfriendとして宣言するように定義されているため、プライベートデータメンバーにアクセスできます。

_#define MYLIB_ENABLE_Tuple(TYPE) friend class TupleConverter<TYPE>;
_

2番目のマクロは、テンプレートの特殊化を導入するために定義されています。

_#define MYLIB_DECLARE_Tuple(TYPE, MEMBERS) \
    template <>                            \
    class TupleConverter<TYPE>             \
    {                                      \
        friend class TYPE;                 \
        static auto toTuple(TYPE& o)       \
            -> decltype(std::tie MEMBERS)  \
        {                                  \
            return std::tie MEMBERS;       \
        }                                  \
    public:                                \
        static auto toTuple(TYPE const& o) \
            -> decltype(std::tie MEMBERS)  \
        {                                  \
            return std::tie MEMBERS;       \
        }                                  \
    };
_

これにより、同じメンバー関数名の2つのオーバーロード、パブリックであるTupleConverter<Record>::toTuple(Record const&)、およびプライベートであり、フレンドシップを通じてRecordのみにアクセスできるTupleConverter<Record>::toTuple(Record&)が作成されます。どちらも、_std::tie_を介して、プライベートデータメンバーへの参照のタプルに変換された引数を返します。 public constオーバーロードはconstへの参照のタプルを返し、private non-constオーバーロードはnon-constへの参照のタプルを返します。

プリプロセッサ置換後、両方のfriend宣言は同じヘッダーファイルで定義されたエンティティを参照するため、他のコードが友情を悪用してカプセル化を解除する可能性はありません。

toTupleは、Recordの定義が完了するまでその戻り値の型を推定できないため、Recordのメンバー関数にすることはできません。

典型的な使用法は次のようになります。

_// lexicographical comparison
bool operator< (Record const& a, Record const& b)
{
    return TupleConverter<Record>::toTuple(a) < TupleConverter<Record>::toTuple(b);
}

// serialization
std::ostream& operator<< (std::ostream& os, Record const& r)
{
    // requires template<class... Ts> ostream& operator<<(ostream&, Tuple<Ts...>) defined elsewhere
    return os << TupleConverter<Record>::toTuple(r);
}
_

これを拡張する方法はたくさんあります。たとえば、データメンバーの名前の_std::vector<std::string>_を返すTupleConverterに別のメンバー関数を追加するなどです。

可変個のマクロを使用することが許可されていれば、解決策はさらに優れているかもしれません。

1
Oktalist

ブーストプリプロセッサを使用して「タプルという名前の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印刷関数が定義されていることを前提としています。

1
Dr.Altan

これを行う別の方法があります。型を定義するのは少し醜いですが、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;

Getをstd :: Tupleと同じように維持するためにメンバー関数にすることを選択しましたが、クラスに簡単に追加できます。 ここにソースコード

これはbrigandメタプログラミングライブラリ( https://github.com/edouarda/brigand )を使用したecatmurの回答に似た実装です。

#include <iostream>
#include <brigand/brigand.hpp>

template<typename Members>
class TaggedTuple{

    template<typename Type>
    struct createMember{
        using type = typename Type::second_type;
    };

    using DataTuple = brigand::transform<Members, createMember<brigand::_1>>;
    using Keys = brigand::keys_as_sequence<Members, brigand::list>;
    brigand::as_Tuple<DataTuple> members;

public:

    template<typename TagType>
    auto& get(){
        using index = brigand::index_of<Keys, TagType>;
        return std::get<index::value>(members);
    }
};

int main(){

    struct FloatTag{};
    struct IntTag{};
    struct DoubleTag{};

    TaggedTuple<brigand::map<
            brigand::pair<FloatTag, float>,
            brigand::pair<IntTag, int>,
            brigand::pair<DoubleTag, double>>> tagged;

    tagged.get<DoubleTag>() = 200;
    auto val = tagged.get<DoubleTag>();
    std::cout << val << std::endl;

    return 0;
}
0
underdoeg