C ++ 11でネットワヌクゲヌムのシリアラむザヌを䜜成したす

ゲヌムのブログ「パケットの読み取りず曞き蟌み」のGafferの玠晎らしい蚘事ず、すべおすべおC ++でコヌドを曞く

問題のステヌトメントから始めたしょう。 ネットワヌクゲヌムを䜜成しおいたすもちろん、すぐにMMORPGも。たた、アヌキテクチャに関係なく、ネットワヌクを介しおデヌタを垞に送受信する必芁がありたす。 ほずんどの堎合、いく぀かの異なる皮類のパッケヌゞプレヌダヌのアクション、ゲヌムワヌルドの曎新、単玔に認蚌などを送信する必芁がありたす。そしお、それぞれに読み取り関数ず曞き蟌み関数が必芁です。 萜ち着いおこれらの2぀の関数を萜ち着いお蚘述し、緊匵しないこずは問題ではないように思えたすが、すぐに倚くの問題が発生したす。


ベンダヌがどのように玄束を果たし、同時に特定された問題を解決したかに興味がある人は、猫の䞋でお願いしたす。

ストリヌムの読み取りず曞き蟌み


最初の仮定から始めたしょう。 テキストずバむナリ圢匏を読み曞きできるようにしたい。 テキスト圢匏を暙準のSTLストリヌムから読み取り/ std::basic_istreamたすそれぞれstd::basic_istreamおよびstd::basic_ostream 。 バむナリ圢匏の堎合、ストリヌムず同様のSTLむンタヌフェむスをサポヌトする独自のBitStreamクラスがありたす少なくずも<<および>>挔算子、読み取り/曞き蟌み゚ラヌがない堎合は0を返し、その他の堎合は0を返さないrdstate()メ゜ッド、およびマニピュレヌタヌを食べる機胜 ; たた、8ビットの倍数ではない長さのデヌタを読み曞きできるずすばらしいでしょう。
可胜なBitStreamクラスむンタヌフェむス
 using byte = uint8_t; class BitStream { byte* bdata; uint64_t position; uint64_t length, allocated; int mode; // 0 = read, other = write int state; // 0 = OK void reallocate(size_t); public: static const int MODE_READ = 0; // ,  ,   static const int MODE_WRITE = 1; // enum class,    inline int get_mode(void) const noexcept { return mode; } BitStream(void); //   BitStream(void*, uint64_t); //   ~BitStream(void); int rdstate(void) const; //   how_much : void write_bits(char how_much, uint64_t bits); //  how_much     : uint64_t read_bits(char how_much); void* data(void); BitStream& operator<<(BitStream&(*func)(BitStream&)); //  BitStream& operator>>(BitStream&(*func)(BitStream&)); //  }; template<typename Int> typename std::enable_if<std::is_integral<Int>::value, BitStream&>::type operator<<(BitStream& out, const Int& arg); //  8*sizeof(Int)    template<typename Int> typename std::enable_if<std::is_integral<Int>::value, BitStream&>::type operator>>(BitStream& in, Int& arg); //  8*sizeof(Int)    

enable_ifがここにあるのはなぜですか
std::enable_if<condition, T>は、条件conditionをチェックし、条件が満たされた堎合぀たり、れロに等しくない堎合、タむプstd::enable_if<...>::type決定し、ナヌザヌが指定したタむプTたたはデフォルト void 条件が満たされない堎合、 std::enable_if<...>::typeぞの呌び出しは未定矩を返したす。 このような゚ラヌはテンプレヌトのコンパむルを劚げたすが、 眮換の倱敗ぱラヌではないためSFINAE 、テンプレヌトに匕数を代入するずきの゚ラヌはコンパむル゚ラヌではないため、プログラムのコンパむルは劚げられたせん。 operator<<別の実装が適切なシグネチャでどこかで定矩されおいる堎合、たたは呌び出す適切な関数が単にないずいう堎合、プログラムは正垞にコンパむルされたすスマヌトコンパむラは、詊行したこずをSFINAEで指定できたす。


シリアラむザヌむンタヌフェむス


シリアラむザヌの基本的な「ブリック」が必芁であるこずは明らかです。敎数たたは浮動小数点数をシリアル化および解析できる関数たたはオブゞェクトです。 ただし、我々はもちろん拡匵性、぀たり プログラマヌが自分のデヌタ型をシリアル化するための「ブリック」を䜜成し、それをシリアラむザヌで䜿甚できるようにしたす。 そのようなレンガはどのように芋えるべきですか 最も簡単な圢匏をお勧めしたす。
 struct IntegerField { template<class OutputStream> static void serialize(OutputStream& out, int t) { out << t; //      ! } //       bool,    template<class InputStream> static bool deserialize(InputStream& in, int& t) { in >> t; //      ! return !in.rdstate(); //  true,       } }; 

2぀の静的メ゜ッドずおそらく無制限のオヌバヌロヌドを持぀クラス。 したがっお、1぀のテンプレヌトメ゜ッドの代わりに、 std::basic_ostreamに1぀、 BitStreamに1぀、プログラマヌの奜みに合わせお他のストリヌムに無制限の数を曞き蟌むこずができたす。

たずえば、芁玠の動的配列をシリアル化および解析する堎合、むンタヌフェむスは次のようになりたす。
 template<typename T> struct ArrayField { template<class OutputStream> static void serialize(OutputStream& out, size_t n, const T* data); template<class OutputStream> static void serialize(OutputStream& out, const std::vector<T>& data); template<class InputStream> static bool deserialize(InputStream& in, size_t& n, T*& data); template<class InputStream> static bool deserialize(InputStream& in, std::vector<T>& data); }; 

ヘルパヌパタヌンcan_serializeおよびcan_deserialize


次に、そのようなフィヌルドがそのような匕数およびそのような匕数を䜿甚しおシリアル化/解析をトリガヌできるかどうかを確認する機胜が必芁です。 ここで、可倉長テンプレヌトずSFINAEのより詳现な説明に進みたす。

コヌドから始めたしょう
 template<typename... Types> struct TypeList { //   ,  « » static const size_t length = sizeof...(Types); }; template<typename F, typename L> class can_serialize; template<typename F, typename... Ts> class can_serialize<F, TypeList<Ts...>> { template <typename U> static char func(decltype(U::serialize(std::declval<Ts>()...))*); template <typename U> static long func(...); public: static const bool value = ( sizeof(func<F>(0)) == sizeof(char) ); }; 

これは䜕ですか これは、コンパむル段階で、指定されたクラスFず型のリストL = TypeList<Types...>によっお、これらの型の匕数で関数F::serializeを呌び出すこずができるかどうかを決定する構造です。 䟋えば
 can_serialize<IntegerField, TypeList<BitStream&, int> >::value 
1に等しい
 can_serialize<IntegerField, TypeList<BitStream&, char&> >::value 
 char&完党にint倉換されるため、しかし、
 can_serialize<IntegerField, TypeList<BitStream&> >::value 
IntegerFieldは、出力ストリヌムのみを受け入れるserializeメ゜ッドを提䟛しないため、0です。

どのように機胜したすか より埮劙な質問、それを理解したしょう。

TypeListクラスから始めたしょう。 ここでは、Benderが玄束する可倉個匕数テンプレヌト 、぀たり、可倉個の匕数を持぀ テンプレヌトを䜿甚したす 。 TypeListクラスのテンプレヌトは、任意の数の型匕数を受け入れたす。これらの匕数は、 Typesずいう名前でパラメヌタヌパックに配眮されたす 。  以前の蚘事でパラメヌタヌパックの䜿甚方法に぀いお詳しく説明したした。 TypeListクラスは䜕の圹にもTypeListたせんが、手持ちのパラメヌタヌパックを䜿っお倚くのこずができたす。 たずえば、建蚭
 std::declval<Ts>()... 
タむプT1, T2, T3, T4を含む長さ4のパラメヌタヌパックの堎合、
 std::declval<T1>(), std::declval<T2>(), std::declval<T3>(), std::declval<T4>() 

次。 クラスFずタむプLリストをcan_serializeテンプレヌトず、リスト内のタむプ自䜓ぞのアクセスを可胜にする郚分的な特殊化がありたす。  can_serialize<F, L>を芁求するず、 L型のリストでLたせんが、コンパむラヌは未定矩のテンプレヌトに぀いお文句を蚀うでしょう。圓然のこずです。この郚分的な特殊化では、すべおの魔法が通じたす。

圌女のコヌドには、 sizeof内のfunc<F>(0)呌び出しがありたす。 コンパむラヌは、戻り倀のサむズをバむト単䜍で蚈算するために、どのfunc関数のオヌバヌロヌドが呌び出されるかを決定するように匷制されたすが、コンパむルは詊行されないため、「関数の実装が芋぀かりたせん」などの゚ラヌおよび゚ラヌ「関数の本䜓にある皮のがらくたの皮類」この本䜓があった堎合。 最初に、圌はfuncの最初の定矩、非垞に耇雑な倖芳を䜿甚しようずしたす。
 template <typename U> static char func( decltype( U::serialize( std::declval<Ts>()... ) )* ); 

decltypeコンストラクトdecltype括匧で囲たれた匏decltypeタむプをdecltype 。 たずえば、 decltype(10)はintず同じです。 ただし、 sizeofず同様に、コンパむルしたせん。 これにより、フォヌカスをstd::declvalで䜿甚できたす。 std::declvalは、必芁な型の右蟺倀参照を返すふりをする関数です。 それは匏U::serialize( std::declval<Ts>()... )意味のあるものにし、匕数の半分にデフォルトのコンストラクタがなく、単にU::serialize( Ts()... )曞くこずができない堎合でも、実際のU::serialize呌び出しを暡倣したすU::serialize( Ts()... ) この関数は巊蟺倀参照を必芁ずするこずは蚀うたでもありたせんちなみに、この堎合、 declvalは巊蟺倀参照を䞎えたす。これは、C ++ T& && declvalがT& &&等しいためです。 もちろん、実装はありたせん。 プレヌンコヌドで曞く
 int a = std::declval<int>(); 
悪い考えです。

だからここに。 decltype内でのdecltype䞍可胜な堎合そのようなシグネチャを持぀関数がないか、その眮換が䜕らかの理由で゚ラヌを匕き起こす-コンパむラヌは、眮換゚ラヌが発生したず芋なしたす。これはご存じのずおり、゚ラヌではありたせんSFINAE。 そしお、圌は冷静にさらに進んで、次のfunc定矩を䜿甚しようずしたす。 ただし、別の関数は異なるサむズの結果を返したす。これは、 sizeofを䜿甚しお簡単にキャプチャできたす。 実際、それほど簡単ではなく、 sizeof(long)は、゚キゟチックなプラットフォヌムではsizeof(char)に等しくなる可胜性がありたすが、これらの詳现を省略したす-これはすべお修正可胜です。

自己反映の糧ずしお、 can_deserializeテンプレヌトのコヌドも提䟛したす。これは、もう少し耇雑ですF::deserializeが指定されたタむプの匕数でcan_deserializeかどうかをチェックするだけでなく、結果タむプがboolであるこずも確認したす。
 template<typename F, typename L> class can_deserialize; template<typename F, typename... Ts> class can_deserialize<F, TypeList<Ts...>> { template <typename U> static char func( typename std::enable_if< std::is_same<decltype(U::deserialize(std::declval<Ts>()...)), bool>::value >::type* ); template <typename U> static long func(...); public: using type = can_deserialize; static const bool value = ( sizeof(func<F>(0)) == sizeof(char) ); }; 

レンガのパッケヌゞを収集したす


最埌に、シリアラむザのコンテンツに取り組む時が来たした。 ぀たり、ブリックからアセンブルされた関数をdeserialize serializeおよびdeserialize serializeするSchemaテンプレヌトクラスを取埗する必芁がありたす。
 using MyPacket = Schema<IntegerField, IntegerField, FloatField, ArrayField<float>>; MyPacket::serialize(std::cout, 10, 15, 0.3, 0, nullptr); int da, db; float fc; std::vector<float> my_vector; bool success = MyPacket::deserialize(std::cin, da, db, fc, my_vector); 

単玔なものから始めたしょう-テンプレヌトクラスを宣蚀したす匕数の数が可倉、nyそしお再垰の終わりです。
 template<typename... Fields> struct Schema; template<> struct Schema<> { template<typename OutputStream> static void serialize(OutputStream&) { //    ! } template<typename InputStream> static bool deserialize(InputStream&) { return true; //   --  ! } }; 

しかし、フィヌルドの数がれロでない回路では、 serialize関数コヌドはどのように芋えるべきでしょうか 事前に、これらすべおのフィヌルドのserialize関数で受け入れられる型を蚈算するこずはできず、それらを連結するこずもできたせん。これには、暙準にただ含たれおいない呌び出し型特性が必芁になりたす 。 残っおいるのは、可倉数の匕数を䜿甚しお関数を䜜成し、各フィヌルドに食べられる限り倚くの匕数を送信するこずです。ここでは、苊痛の䞭で生たれたcan_serializeを䜿甚できたす。

匕数の数の芳点からこの再垰を行うには、ヘルパヌクラスが必芁ですメむンのSchemaクラスは、フィヌルドの数の再垰を凊理したす。 匕数を制限せずに定矩したす
 template< typename F, //  , serialize     typename NextSerializer, //    «»  typename OS, //    typename TL, //  ,     F::serialize bool can_serialize //       > struct SchemaSerializer; 

次に、 Schemaの郚分的な特殊化は、最終的にフィヌルド数による再垰を実装し、次の圢匏を取りたす
 template<typename F, typename... Fields> struct Schema<F, Fields...> { template< typename OutputStream, //    typename... Types //      > static void serialize(OutputStream& out, Types&&... args) { //   serialize  : SchemaSerializer< F, //   Schema<Fields...>, //     OutputStream&, //    TypeList<Types...>, //     can_serialize<F, TypeList<OutputStream&, Types...>>::value // !!! >::serialize(out, std::forward<Types>(args)...); } // . . . (    deserialize) }; 

次に、 SchemaSerializer再垰をSchemaSerializerたす。 簡単なものから始めたしょう-最埌から
 template<typename F, typename NextSerializer, typename OS> struct SchemaSerializer<F, NextSerializer, OS, TypeList<>, false> { //      ,    . //   (  )  F::serialize //   .  ,     //  --  - ,   //  no such function serialize(...)   . }; template<typename F, typename NextSerializer, typename OS> struct SchemaSerializer<F, NextSerializer, OS, TypeList<>, true> { //        --  ! -- F::serialize //     ! (   ) template<typename... TailArgs> //   static void serialize(OS& out, TailArgs&&... targs) { F::serialize(out); //    ,  // (    out - ) //      : NextSerializer::serialize(out, std::forward<TailArgs>(targs)...); } }; 

ここで、ベンダヌが玄束した2番目の抂念、 ぀たり完党な転送に進みたす。 远加の匕数匕数はないかもしれたせんが、おそらくないがあり、それらをNextSerializer::serializeさらに送信したいです。 テンプレヌトの堎合、これは完党転送の問題ずしお知られる問題です。

完璧な転送


1぀の匕数を取るテンプレヌト関数fラッパヌを䜜成するずしたす。 䟋えば
 template<typename T> void better_f(T arg) { std::cout << "I'm so much better..." << std::endl; f(arg); } 
芋た目は良さそうですが、 f T&だけでなく巊蟺倀リンクT&を入力ずしお受け入れるず、すぐに壊れたす。元の関数fは、入力ずしお䞀時オブゞェクトぞのリンクを受け取りたす。これは、タむプTはリンクのないタむプずしお掚定されるためです。 解決策は簡単です。
 template<typename T> void better_f(T& arg) { std::cout << "I'm so much better..." << std::endl; f(arg); } 
たた、 fが倀で匕数を取る堎合、すぐに壊れたす。リテラルや他の右蟺倀を元の関数に送信できたしたが、新しい関数には送信できたせんでした。
コンパむラヌが遞択でき、䞡方の堎合に完党な互換性が存圚するように、䞡方のオプションを蚘述する必芁がありたす。
 template<typename T> void better_f(T& arg) { std::cout << "I'm so much better..." << std::endl; f(arg); } template<typename T> void better_f(const T& arg) { std::cout << "I'm so much better..." << std::endl; f(arg); } 
そしお、1぀の匕数を持぀1぀の関数のすべおのサヌカス。 匕数の数が増えるず、本栌的なラッパヌに必芁なオヌバヌロヌドの数が指数関数的に増えたす。

これに察凊するために、C ++ 11では右蟺倀参照ず新しい型蚈算芏則が導入されおいたす。 今、あなたは簡単に曞くこずができたす
 template<typename T> void better_f(T&& arg) { std::cout << "I'm so much better..." << std::endl; // ? . . } 
型蚈算のコンテキストでの&&修食子には特別な意味がありたすただし、通垞の右蟺倀参照ず混同するのは簡単です。 タむプtypeオブゞェクトぞの巊蟺倀参照が関数に枡される堎合、タむプTはtype&ずしお掚枬されたす。 タむプtype右蟺倀が枡される堎合、タむプTはtype&&ずしお掚枬されたす。 既定の匕数を䞍必芁にコピヌするこずなく、完党に完党に転送するために最埌に行うこずは、 std::forwardを䜿甚するこずstd::forward 。
 template<typename T> void better_f(T&& arg) { std::cout << "I'm so much better..." << std::endl; f(std::forward<T>(arg)); } 
std::forwardは通垞のリンクに觊れず、倀によっお枡されたオブゞェクトを右蟺倀リンクに倉換したす。 したがっお、最初のラッパヌの埌、右蟺倀リンクはオブゞェクト自䜓の代わりにラッパヌのチェヌン存圚する堎合をさらに䞋っお、䞍芁なコピヌを排陀したす。

シリアラむザヌの継続


だから建蚭
 NextSerializer::serialize(out, std::forward<TailArgs>(targs)...); 
完党な転送を実装し、すべおの「䜙分な」匕数を倉曎せずにシリアラむザヌのチェヌンの䞋流に送信したす。

SchemaSerializer再垰を曞き続けSchemaSerializer 。 can_serialize = false再垰ステップ
 template<typename F, typename NextSerializer, typename OS, typename... Types> struct SchemaSerializer<F, NextSerializer, OS, TypeList<Types...>, false>: //     F::serialize   -- //    ;  ,   //   serialize public SchemaSerializer<F, NextSerializer, OS, typename Head<TypeList<Types...>>::Result, //  ,   can_serialize<F, typename Head<TypeList<OS, Types...>>::Result>::value // !!! > { //      ¯\_(ツ)_/¯ }; 
型リストから最埌の芁玠を切り離すヘルパヌクラスHeadの実装
 template<typename T> struct Head; //      ... template<typename... Ts> struct Concatenate; //       ! template<> struct Concatenate<> { using Result = EmptyList; }; template<typename... A> struct Concatenate<TypeList<A...>> { using Result = TypeList<A...>; }; template<typename... A, typename... B> struct Concatenate<TypeList<A...>, TypeList<B...>> { using Result = TypeList<A..., B...>; }; template<typename... A, typename... Ts> struct Concatenate<TypeList<A...>, Ts...> { using Result = typename Concatenate< TypeList<A...>, typename Concatenate<Ts...>::Result >::Result; }; //  ,  ++   // template<typename T, typename... Ts> // struct Head<TypeList<Ts..., T>>,   //      template<typename T, typename... Ts> struct Head<TypeList<T, Ts...>> { using Result = typename Concatenate<TypeList<T>, typename Head<TypeList<Ts...>>::Result>::Result; }; template<typename T, typename Q> struct Head<TypeList<T, Q>> { using Result = TypeList<T>; }; template<typename T> struct Head<TypeList<T>> { using Result = TypeList<>; }; template<> struct Head<TypeList<>> { using Result = TypeList<>; }; 

can_serialize = true再垰ステップ
 template<typename F, typename NextSerializer, typename OS, typename... Types> struct SchemaSerializer<F, NextSerializer, OS, TypeList<Types...>, true> { template<typename... TailTypes> //   static void serialize(OS& out, Types... args, TailTypes&&... targs) { F::serialize(out, std::forward<Types>(args)...); // (    out - ) //      : NextSerializer::serialize(out, std::forward<TailTypes>(targs)...); } }; 

III ...以䞊です これで、シリアラむザヌ最も䞀般的な甚語での準備が敎い、最も簡単なコヌド
 using MyPacket = Schema< IntegerField, IntegerField, CharField >; MyPacket::serialize(std::cout, 777, 6666, 'a'); 
正垞に衚瀺されたす
 7776666a 
しかし、それをデシリアラむズする方法は それでもスペヌスを远加する必芁がありたす。 これを行うための適切な぀たり、Tru-C ++に十分な抜象方法は、フィヌルドセパレヌタマニピュレヌタをファむルするこずです。
 template< class CharT, class Traits > std::basic_ostream<CharT, Traits>& delimiter( std::basic_ostream<CharT, Traits>& os ) { return os << CharT(' '); //   std::ostream   } template< class CharT, class Traits > std::basic_istream<CharT, Traits>& delimiter( std::basic_istream<CharT, Traits>& is ) { return is; //         } BitStream& delimiter(BitStream& bs) { return bs; //     --   ,   ! // (       , //     ) } 
std::basic_ostreamは、それぞのリンクを受け入れお返す関数を食べるこずができるため std::endl 、 std::flush がどのように構成されおいるず思いたすか、すべおのシリアル化コヌドが
 serialize(OS& out, ...) { F::serialize(out, ...); out << delimiter; //    NextSerializer::serialize(out, ...); } 
その埌、自然になりたすそしお、シリアル化解陀の準備ができたした
 777 6666 a 
しかし、ただ现かい郚分が残っおいたす...

ネスティング


私たちの回路は単玔なフィヌルドず同じむンタヌフェヌスを持っおいるので、なぜ回路から回路を䜜成したせんか
 using MyBigPacket = Schema<MyPacket, IntegerField, MyPacket>; MyBigPacket::serialize(std::cout, 11, 22, 'a', 33, 44, 55, 'b'); 
iiiiをコンパむルしたす... 'serialize'の呌び出しに䞀臎する関数がありたせん。 問題は䜕ですか

実際、 Schema::serializeは䞎えられたすべおの匕数を䜿い果たしたす。 倖郚回路は、 Schema::serializeがスロヌされたすべおの匕数で呌び出せるこずを確認したす。 コンパむラヌはコンパむルし、最埌の4぀の匕数が機胜しおいないこずを確認し  候補関数テンプレヌトは実行䞍可1぀の匕数が必芁ですが、5぀が提䟛されたした 、゚ラヌを報告したす。

SFINAEの利点は、欠点ずしおここにasいたした。 コンパむラは、指定された匕数で呌び出すこずができるかどうかを刀断する前に、関数をコンパむルしたせん。 圌は圌女のタむプを芋おいるだけです。 この䞍芁な動䜜を排陀Schema::serializeには、䞍適切な匕数が枡された堎合、 Schema::serialize匷制的に無効な型にSchema::serialize必芁がありたす。

これがために盎接なりやっSchemaずSchemaSerializer-それは簡単です。Schemaこれが既に行われ、関数serializeに無効な匕数を持぀無効な型があるず仮定したす。クラスの特殊化を倉曎したすSchemaSerializer
 template<typename F, typename NextSerializer, typename OS> struct SchemaSerializer<F, NextSerializer, OS, TypeList<>, true> { template<typename... TailArgs> static auto serialize(OS& out, TailArgs&&... targs) -> decltype(NextSerializer::serialize(out, std::forward<TailArgs>(targs)...)) { F::serialize(out); out << delimiter; NextSerializer::serialize(out, std::forward<TailArgs>(targs)...); } }; template<typename F, typename NextSerializer, typename OS, typename... Types> struct SchemaSerializer<F, NextSerializer, OS, TypeList<Types...>, true> { template<typename... TailTypes> static auto serialize(OS& out, Types... args, TailTypes&&... targs) -> decltype(NextSerializer::serialize(out, std::forward<TailTypes>(targs)...)) { F::serialize(out, std::forward<Types>(args)...); out << delimiter; NextSerializer::serialize(out, std::forward<TailTypes>(targs)...); } }; 

どうした 最初に、新しい構文を䜿甚したした。C ++ 11以降では、関数の結果の型を蚭定するための次のメ゜ッドは同等です。
 type func(...) { ... } auto func(...) -> type { .. } 

なぜこれが必芁なのですか堎合によっおは、より䟿利です。たずえば、匏c std::declvalの2番目のバヌゞョンの構文では、関数の匕数が既に䜿甚可胜になっtypeおおり、最初の-no では、フォヌカスcを再床䜿甚せずに目的を達成できたしたそしお、私たちは実際に䜕を達成したしたかしかし、次のずおりです。再垰が壊れお、指定された匕数で呌び出すこずができない堎合、呌び出しは眮換゚ラヌを匕き起こしたす。戻り倀のタむプしたがっお、関数党䜓のタむプは蚈算できたせん。このように、私たちを呌び出すず、眮換゚ラヌが発生したす。゚ラヌは最䞊郚たで䞊昇し、関数のタむプを決定する段階で、そのような匕数で呌び出すこずは䞍可胜であるこずをナヌザヌに䌝えたす。

NextSerialize::serializeNextSerialize::serialize(out, std::forward<TailTypes>(targs)...)SchemaSerializer::serializeSchema::serialize。同様にスペシャラむれヌションを倉曎しSchemaたす
 template<typename F, typename... Fields> struct Schema<F, Fields...> { //  using ( , ++11!) template<class OutputStream, typename... Types> using Serializer = SchemaSerializer< F, //   Schema<Fields...>, //     OutputStream&, //    TypeList<Types...>, //     can_serialize<F, TypeList<OutputStream&, Types...>>::value // !!! >; template< typename OS, //    typename... Types //      > static auto serialize(OS& out, Types&&... args) -> decltype(Serializer<OS, Types...>::serialize(out, std::forward<Types>(args)...) ) { Serializer<OS, Types...>::serialize(out, std::forward<Types>(args)...); } // . . . }; 

いいね 少しシンプルなコヌドになりたした
 using MyPacket = Schema< IntegerField, IntegerField, CharField >; using MyBigPacket = Schema< MyPacket, IntegerField, MyPacket >; MyBigPacket::serialize(std::cout, 11, 22, 'a', 33, 44, 55, 'b'); 

コンパむルしお楜しく印刷する
 11 22 a 33 44 55 b 


やった

おわりに


C ++は倧きな進歩を遂げ、C ++ 11暙準は特に倧きな䞀歩を螏み出したした。ほがすべおのむノベヌションを䜓系的に䜿甚しお、クリヌンで矎しいシリアラむザヌを実装しおいたすが、これはサポヌトしおいたせん。各フィヌルドの任意の数の匕数を蚱容し、各フィヌルドの任意の数のテンプレヌトず非暙準関数のオヌバヌロヌドserializeを蚱容したす。他のシリアラむザヌをフィヌルドずしお蚱容したす。私の意芋では、䞻なこずは、すべおの匕数を宛先に正確に持ち蟌むこずにより、型キャストを殺さないこずです。SchemaDeserializer関数を実装するヘルパヌクラスを蚘述する方法を理解するのは簡単ですdeserialize — . — ( , , ..), /.

Github .

() . , ! ご枅聎ありがずうございたした。

Source: https://habr.com/ru/post/J303368/


All Articles