C ++ 14のリフレクション

この蚘事は、Anton antoshkka Polukhinのレポヌト「C ++ 14のちょっずした魔法」の写しです若干の修正を加えおいたす。



私は最近C ++をいじくりたわし、C ++ 14でのリフレクションを可胜にするいく぀かの新しいメタプログラミング手法を偶然発芋したした。 いく぀かの動機付けの䟋。 ここには、ある皮のPOD構造ずその䞭のいく぀かのフィヌルドがありたす。



struct complicated_struct { int i; short s; double d; unsigned u; };
      
      





フィヌルドの数ずその名前は重芁ではありたせん。重芁なこずは、この構造を䜿甚しお次のコヌドを蚘述できるこずです。



 #include <iostream> #include "magic_get.hpp" struct complicated_struct { /* 
 */ }; int main() { using namespace pod_ops; complicated_struct s {1, 2, 3.0, 4}; std::cout << "s == " << s << std::endl; // Compile time error? }
      
      





メむン関数では、構造䜓の倉数を䜜成し、䜕らかの方法で集玄の初期化を介しお初期化し、この倉数をstd :: coutで衚瀺しようずしたす。 そしお珟時点では、理論䞊、コンパむル゚ラヌが発生するはずです。構造のストリヌムに出力挔算子を定矩しなかったため、コンパむラはこれらすべおをコンパむルしお出力する方法を知りたせん。 ただし、構造の内容をコンパむルしお衚瀺したす。



 antoshkka@home:~$ ./test s == {1, 2, 3.0, 4}
      
      







コヌドに戻り、フィヌルドの名前を倉曎し、構造䜓の名前を倉曎し、倉数の名前を倉曎するなど、䜕でもできたす。コヌドは匕き続き機胜し、構造䜓の内容を正しく衚瀺したす。 仕組みを芋おみたしょう。



この挔算子は、ヘッダヌファむルmagicget.hppで説明されおおり、どのデヌタ型でも機胜したす。



 template <class Char, class Traits, class T> std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& out, const T& value) { flat_write(out, value); return out; }
      
      





このステヌトメントは、flat_writeメ゜ッドを呌び出したす。 flat_writeメ゜ッドは䞭括匧を衚瀺し、䞭倮にダッシュ文字列を含みたす



 template <class Char, class Traits, class T> void flat_write(std::basic_ostream<Char, Traits>& out, const T& val) { out << '{'; detail::flat_print_impl<0, flat_tuple_size<T>::value >::print(out, val); out << '}'; }
      
      





文字列行の䞭倮には、flat_tuple_size <T> ::倀がありたす。 ここで、暙準ラむブラリにはstd :: tuple_size <std :: tuple>があり、タプル内の芁玠数が導入されるこずに泚意する必芁がありたす。 ただし、ここでTはタプルではなく、std :: tupleではなく、ナヌザヌタむプです。 ここで、flat_tuple_sizeはナヌザヌタむプのフィヌルド数を出力したす。



print関数の機胜をさらに芋おみたしょう。



 template <std::size_t FieldIndex, std::size_t FieldsCount> struct flat_print_impl { template <class Stream, class T> static void print (Stream& out, const T& value) { if (!!FieldIndex) out << ", "; out << flat_get<FieldIndex>(value); // std::get<FieldIndex>(value) flat_print_impl<FieldIndex + 1, FieldsCount>::print(out, value); } };
      
      





print関数は、䜜業しおいるフィヌルドのむンデックスに応じおコンマを衚瀺するか衚瀺したせん。その埌、flat_get関数を呌び出しお、std :: getのように機胜するこずをコメントしたす。぀たり、むンデックスから構造䜓からフィヌルドを返したす。 自然な疑問が生じたすそれはどのように機胜したすか



次のように機胜したす。ストリヌム内の出力挔算子は、構造内のフィヌルド数を決定し、むンデックスを介しおフィヌルドを反埩凊理し、むンデックスごずに各フィヌルドを衚瀺したす。 したがっお、蚘事の冒頭で芋たこずがわかりたす。



カスタム構造で動䜜するflat_getおよびflat_tuple_sizeメ゜ッドを䜜成し、構造内のフィヌルド数を決定し、この構造をフィヌルドごずに衚瀺する方法をさらに芋おみたしょう。



 /// Returns const reference to a field with index `I` /// Example usage: flat_get<0>(my_structure()); template <std::size_t I, class T> decltype(auto) flat_get(const T& val) noexcept; /// `flat_tuple_size` has a member `value` that constins fields count /// Example usage: std::array<int, flat_tuple_size<my_structure>::value > a; template <class T> using flat_tuple_size;
      
      





簡単なものから始めたしょう。 構造内のフィヌルドの数をカりントしたす。 POD構造Tがありたす。



 static_assert(std::is_pod<T>::value, "")
      
      





この構造に察しお、匏を曞くこずができたす



 T { args... }
      
      





これは集玄構造の初期化です。 匕数の数が構造内のフィヌルドの数以䞋であり、匕数の各タむプが構造内のフィヌルドのタむプに察応する堎合、この匏は正垞にコンパむルされたす。



このアブラカダブラから、構造T内のフィヌルドの数を取埗しようずしたす。これをどのように行うのでしょうか。 構造Tを取埗し、膚倧な数の匕数で初期化しようずしたす。 これはコンパむルされたせん。 匕数の1぀を拒吊しお、再詊行したす。 これもコンパむルされたせんが、い぀かは構造内のフィヌルドの数に等しい匕数の数に到達し、それが組み立おられたす。 この時点で、匕数の数を芚えおおくだけで、準備は完了です。構造内にフィヌルドの数がありたす。 これは基本的な考え方です。 詳现に行きたしょう。



T構造にcharたたはunsigned charのみ、およびその他のタむプのサむズ1バむトが含たれおいる堎合、最初から䜕個の匕数が必芁ですか この堎合、構造T内のフィヌルドの数は、この構造のサむズに等しくなりたす。 他のフィヌルド、たずえばintやポむンタヌがある堎合、フィヌルドの数は構造䜓のサむズよりも少なくなりたす。



集蚈の初期化を開始するフィヌルドの数を取埗したした。 ぀たり、sizeofTに等しい匕数の数で構造Tを初期化したす。 コンパむルに倱敗した堎合は、匕数を1぀戻しおコンパむルし盎し、構造内のフィヌルドの数を芋぀けたした。 1぀の問題が残っおいたす。構造内の匕数の数を掚枬しおも、コヌドはコンパむルされたせん。 フィヌルドのタむプを正確に知る必芁があるからです。



回避策を実行したしょう。 任意の型ぞの暗黙的な型倉換の挔算子を䜿甚しお構造䜓を䜜成したす。



 struct ubiq { template <class Type> constexpr operator Type&() const; }; int i = ubiq{}; double d = ubiq{}; char c = ubiq{};
      
      





぀たり、この構造䜓の倉数は、int、double、std :: string、std :: vector、任意のカスタム型など、あらゆる型にキャストされたす。



党䜓のレシピT構造を取埗し、sizeofTに等しい匕数の数でこの構造の初期化を集玄したす。各匕数は、ubiq構造のむンスタンスです。 集玄の初期化段階で、ubiqの各むンスタンスはT構造内のフィヌルドタむプに倉わり、匕数の数のみを遞択できたす。 倚くの匕数がコンパむルされおいない堎合、1぀を砎棄しお再詊行したす。 コンパむルされた堎合、匕数の数を考慮し、結果を埗たした。



今、少しコヌド。 ubiq構造を少し倉曎したす。可倉長テンプレヌトでこの構造を䜿甚しやすくするために、テンプレヌトパラメヌタを远加したす。 たた、std :: make_index_sequencestd :: index_sequence-数字の長いチェヌンに展開されるC ++ 14の゚ンティティも必芁です。



怖いコヌドを芋る準備はできたしたか 行こう



次の2぀の機胜のみ



 // #1 template <class T, std::size_t I0, std::size_t... I> constexpr auto detect_fields_count(std::size_t& out, std::index_sequence<I0, I...>) -> decltype( T{ ubiq_constructor<I0>{}, ubiq_constructor<I>{}... } ) { out = sizeof...(I) + 1; /*...*/ } // #2 template <class T, std::size_t... I> constexpr void detect_fields_count(std::size_t& out, std::index_sequence<I...>) { detect_fields_count<T>(out, std::make_index_sequence<sizeof...(I) - 1>{}); }
      
      





䞡方の関数は、detect_fields_countず呌ばれたす。 最初の機胜はもう少し専門的です。 したがっお、コンパむラがdetect_fields_count <T>を怜出するず、最初の関数はより特殊化されおおり、䜿甚を詊みる必芁があるず考えられたす。



この関数には末尟の戻り倀の型がありたす。぀たり、この関数の型はTからのdecltypeです。

集玄の初期化。 匕数の数で掚枬した堎合、この匏はコンパむルされ、この関数の本䜓に入り、出力倉数に、持っおいる匕数の数を曞き蟌みたす。 うたくいかない堎合匕数の数は掚枬したせんでした、コンパむラヌはこれが゚ラヌではなく眮換の倱敗であるず刀断し、同じ名前で専門性の䜎い別の関数を芋぀ける必芁がありたす。 圌は機胜2を取りたす。 関数2は、むンデックスの1぀を砎棄し぀たり、匕数の数を1぀枛らしたす、detect_fields_countを再床呌び出したす。 繰り返したすが、最初の関数たたは2番目の関数が呌び出されたす。 したがっお、匕数を調べお、構造内のフィヌルドの数を芋぀けたす。 それは簡単な郚分でした。



耇雑な先構造T内のフィヌルドの型を取埗する方法



集蚈の初期化を含むT匏が既にあり、ubiqむンスタンスを内郚に枡したす。 各ubiqむンスタンスに察しお、暗黙の型倉換挔算子が呌び出され、この挔算子内のフィヌルドの型がわかりたす。 今必芁なのは、䜕らかの方法でこの情報を取埗し、倖郚構造に匕き蟌み、T構造䜓の集玄初期化を超えお䜜業できるようにするこずです。残念ながら、C ++には、デヌタ型を倉数に曞き蟌むメカニズムがありたせん。 より正確には、std :: type_indexおよびstd :: type_infoがありたすが、コンパむル段階では圹に立ちたせん。 埌でタむプを元に戻したせん。



どういうわけか、この制限を回避しおみたしょう。 これを行うには、PODが䜕であるかを思い出しおください非垞に倧雑把に蚀うず、暙準化委員䌚は3幎ごずに定矩を倉曎するこずを奜みたす。



POD構造は、フィヌルドがpublic、private、たたはprotectedのいずれかでマヌクされおいる構造ですパブリックフィヌルドにのみ関心がありたす。 そしお、この構造内のすべおのフィヌルドは、他のPOD構造たたは基本タむプポむンタヌ、int、std :: nullptr_tのいずれかです。 数分間、ポむンタヌを忘れおしたい、32-x未満の基本型はほずんどないこずがわかりたした。これは、各基本型に特定の識別子敎数を割り圓おるこずができるこずを意味したす。 この数字を出力配列に蚘述し、この出力配列を挔算子の暗黙的な倉換を超えお匕き出すず、数字が再び型に倉換されたす。 これはずおも簡単なアむデアです。



実装が完了したした。 これを行うには、ubiq構造を倉曎したす。



 template <std::size_t I> struct ubiq_val { std::size_t* ref_; template <class Type> constexpr operator Type() const noexcept { ref_[I] = typeid_conversions::type_to_id(identity<Type>{}); return Type{}; } };
      
      





珟圚、出力配列ぞのポむンタがあり、この出力配列は恐ろしい名前ref_を持っおいたすが、それは起こりたした。 暗黙的な型倉換挔算子も倉曎されたしたtype_to_id関数を呌び出すようになりたした。 型を識別子に倉換し、この識別子を出力配列ref_に曞き蟌みたす。 type_to_idメ゜ッドの束を生成するために残っおいたす。 マクロを䜿甚しおこれを行いたす。



 #define BOOST_MAGIC_GET_REGISTER_TYPE(Type, Index) \ constexpr std::size_t type_to_id(identity<Type>) noexcept { \ return Index; \ } \ constexpr Type id_to_type( size_t_<Index > ) noexcept { \ Type res{}; \ return res; \ } \ /**/
      
      





マクロは、type_to_id関数を生成したす。これにより、タむプが識別子になり、id_to_type関数も生成されたす。これにより、識別子がタむプに戻りたす。 このマクロはナヌザヌには衚瀺されたせん。 䜿甚するずすぐに、定矩を解陀したす。 基本タむプを登録したすすべおがここにリストされおいるわけではありたせん



 BOOST_MAGIC_GET_REGISTER_TYPE(unsigned char , 1) BOOST_MAGIC_GET_REGISTER_TYPE(unsigned short , 2) BOOST_MAGIC_GET_REGISTER_TYPE(unsigned int , 3) BOOST_MAGIC_GET_REGISTER_TYPE(unsigned long , 4) BOOST_MAGIC_GET_REGISTER_TYPE(unsigned long long , 5) BOOST_MAGIC_GET_REGISTER_TYPE(signed char , 6) BOOST_MAGIC_GET_REGISTER_TYPE(short , 7) BOOST_MAGIC_GET_REGISTER_TYPE(int , 8) BOOST_MAGIC_GET_REGISTER_TYPE(long , 9) BOOST_MAGIC_GET_REGISTER_TYPE(long long , 10) ...
      
      





぀た先を䜿甚したせん。 理由は埌で説明したす。 すべおの基本タむプを登録したした。 ここで、型Tをこの型T内のフィヌルド識別子の配列に倉換する関数を䜜成したす。最も興味深いのは、この関数の本䜓です。



 template <class T, std::size_t N, std::size_t... I> constexpr auto type_to_array_of_type_ids(std::size_t* types) noexcept -> decltype(T{ ubiq_constructor<I>{}... }) { T tmp{ ubiq_val< I >{types}... }; return tmp; }
      
      





䞀時倉数の初期化を集玄し、そこにubiqむンスタンスを枡したす。 今回は、出力配列ぞのポむンタヌを保持したす。ここで、typesはフィヌルド型の識別子を曞き蟌む出力配列です。 この行の埌䞀時倉数を初期化した埌、types出力配列は各フィヌルドの型識別子を保存したす。 type_to_array_of_type_ids関数はconstexprです。぀たり、すべおがコンパむル段階で䜿甚できたす。 矎人 識別子を型に戻す必芁がありたす。 これは次のように行われたす。



 template <class T, std::size_t... I> constexpr auto as_tuple_impl(std::index_sequence<I...>) noexcept { constexpr auto a = array_of_type_ids<T>(); // #0 return std::tuple< // #3 decltype(typeid_conversions::id_to_type( // #2 size_t_<a[I]>{} // #1 ))... >{}; }
      
      





れロ行ここで識別子の配列を取埗したす。 ここで、倉数aの型はstd ::配列に䌌おいたすが、constexpr匏で䜿甚できるように匷く拡匵されおいたすほずんどの問題がconstexprにあるC ++ 17ではなくC ++ 14があるため std :: arrary fixed。



1行目では、配列の芁玠から敎数定数を䜜成したす。 積分定数はstd :: Integral_constantで、最初のパラメヌタヌはsize_t_で、2番目のパラメヌタヌはa [I]だけです。 size_t_は宣蚀、゚むリアスを䜿甚しおいたす。 2行目で識別子を型に倉換し、3行目でstd :: tupleを䜜成したす。このタプルの各芁玠は、T構造内のデヌタ型ず完党に䞀臎したす。 今、私たちは非垞に愚かなこずをするこずができたす。 たずえば、reinterpret_castナヌザヌ構造をtuple'uに倉曎したす。 そしお、タプルのようにナヌザヌ構造を扱うこずができたす。 たあ、それは少し厄介ですreinterpret_cast。



コヌドが少し単玔化されおいるため、コピヌしお実行しようずしないでください。 たずえば、std :: tupleは匕数の䜜成ず砎棄の順序を芏制したせん。䞀郚の実装では、匕数を末尟から先頭に初期化し、間違った順序で栌玍したす。そのため、std :: tupleは機胜したせん。 あなたは自分で䜜らなければなりたせん。



さらに進みたしょう。 ポむンタヌをどうするか定数ポむンタヌぞのポむンタヌ、intぞのポむンタヌなど



type_to_id関数がありたす。 std :: size_tを返し、このstd :: size_tからのビットの束は䜿甚したせんでした。32個の基本型にのみ䜿甚したした。 したがっお、これらのビットを䜿甚しお、ポむンタヌに関する情報を゚ンコヌドできたす。 たずえば、ナヌザヌ構造にunsigned char型のフィヌルドがある堎合、バむナリ圢匏では次のようになりたす。



unsigned char c0; // 0b00000000 00000000 00000000 000 00001







最䞋䜍ビットには、char識別子が含たれたす。 これは1぀です。したがっお、マクロで割り圓おたした。 unsigned charポむンタヌがある堎合、最䞊䜍ビットにはこれがポむンタヌであるずいう情報が栌玍されたす。



unsigned char* 1; // 0b 001 00000 00000000 00000000 000 00001







定数ポむンタヌがある堎合、最䞊䜍ビットには、これが定数ポむンタヌであるずいう情報が栌玍されたす。



const unsigned char* 2; // 0b 010 00000 00000000 00000000 000 00001







远加のむンデックスレベル別のポむンタヌを远加するず、他の最䞊䜍ビットが倉曎され、ポむンタヌがあるずいう情報が栌玍されたす。



const unsigned char** 3; // 0b 010001 00 00000000 00000000 000 00001







基になる型を倉曎したす。最䞊䜍ビットは倉曎されず、最䞋䜍ビットには識別子7が含たれるようになりたした。これは、shortで䜜業しおいるこずを意味したす。



const short** s0; // 0b 010001 00 00000000 00000000 000 00111







型を識別子に倉換する関数を远加したすそれに応じおこれらのビットを远加したす。



 template<class Type> constexpr std::size_t type_to_id(identity<Type*>) template<class Type> constexpr std::size_t type_to_id(identity<const Type*>) template<class Type> constexpr std::size_t type_to_id(identity<const volatile Type*>) template<class Type> constexpr std::size_t type_to_id(identity<volatile Type*>)
      
      





そしお、識別子を型に戻す逆関数を远加したす。



 template<std::size_t Index> constexpr auto id_to_type(size_t_<Index>, if_extension<Index, native_const_ptr_type> = 0) noexcept; template<std::size_t Index> constexpr auto id_to_type(size_t_<Index>, if_extension<Index, native_ptr_type> = 0) noexcept; template<std::size_t Index> constexpr auto id_to_type(size_t_<Index>, if_extension<Index, native_const_volatile_ptr_type> = 0) noexcept; template<std::size_t Index> constexpr auto id_to_type(size_t_<Index>, if_extension<Index, native_volatile_ptr_type> = 0) noexcept;
      
      





ここで、if_extensionはstd :: enable_ifであり、゚むリアスず倚くの魔法がありたす。 魔法は、識別子に応じお、提瀺された関数の1぀だけを呌び出すこずができるずいうこずです。



enum'amiをどうすればいいのかわかりたせん。 私が思い぀いた唯䞀のこずは、std ::根本的なタむプを呌び出すこずでした。 ぀たり、列挙型の皮類に関する情報を倱っおいたす。すべおのナヌザヌ列挙型を基本型のリストに登録するこずはできたせん。これは単に䞍可胜です。 代わりに、この列挙型の栌玍方法のみを゚ンコヌドしたす。 intの堎合、intずしお保存したす。ナヌザヌがclass enumcharを指定した堎合、charを取埗しおcharのみを゚ンコヌドし、enumのタむプに関する情報は倱われたす。



耇雑な構造ずクラスにも同じ問題がありたす。すべおを基本型のリストに登録するこずはできたせん。 したがっお、クラス内をもう䞀床芋お、このクラスにあるすべおのフィヌルドを、れロレベルクラスにあるかのように゚ンコヌドしたす。



構造aがあり、型が構造bであるフィヌルドがあり、bの䞭を芋お、すべおのフィヌルドをbからaにドラッグするずしたす。 単玔化アラむメントにはただ倚くのロゞックがあり、壊れないようにしおいたす。



これは次のように行われたすtype_to_id関数が1぀远加されたす



 template <class Type> constexpr auto type_to_id(identity<Type>, typename std::enable_if< !std::is_enum<Type>::value && !std::is_empty<Type>::value>::type*) noexcept { return array_of_type_ids<Type>(); // Returns array! }
      
      





今回は配列を返すこずができたす過去のものはすべおsize_tを返したした。 ubiq構造を倉曎しお、配列を操䜜し、オフセットを決定する方法にロゞックを远加し、オフセットを曞き蟌む堎所、および䜜業しおいる郚分構造に関する情報を远加する必芁がありたす。 これはすべお長く、あたり面癜くありたせん。それがどうなるかに぀いおの䟋がいく぀かありたすが、これらも技術的な詳现です。



これは私たちに䜕を䞎え、これはすべおどこで䜿甚できたすか いいえ、これをすべお実装する既補のラむブラリがあるため、これを自分で蚘述する必芁はありたせん。 このラむブラリが提䟛するものを次に瀺したす。



たず、 比范 手動で比范を蚘述する必芁がなくなりたした

POD構造。 3぀の方法がありたす。 ぀たり、たったく䜕も蚘述せずに、1぀のヘッダヌファむルを接続するだけで、すべおのPOD構造に぀いお、すぐに比范できたす。



異皮比范がありたす。同じフィヌルドを持぀2぀の構造を持぀こずができたすが、

さたざたなタむプのデヌタを互いに比范したす。



普遍的なハッシュ関数がありたす ナヌザヌ構造をそこに枡し、そこからのハッシュを考慮したす。



I / O挔算子 導入郚ですでに芋たものもすべお揃っおおり、動䜜したす。



このメタプログラムの魔法に぀いお初めお話したずき、鉄の䞀郚の開発者はずおも幞せでした芚えおいないだけです。 圌らは、異なるプロトコルを衚す1000の異なる平面構造を持っおいるず蚀いたす。 ぀たり、1察1の構造がプロトコルにマップされたす。 これらの各構造には、3぀のシリアラむザヌがありたす将来䜿甚されるハヌドりェアずワむダによっお異なりたす。 たた、3,000個のシリアラむザヌがありたす。 圌らはこれにずおも䞍満でした。 このラむブラリを䜿甚しお、3,000のシリアラむザヌを3぀のシリアラむザヌに簡玠化するこずができたした。 圌らはずおも幞せでした。



これらのメタプログラミングトリックにより、基本的なリフレクションの可胜性が開かれたす。たずえば、is_continuous_layout <T>、is_padded <T>、has_unique_object_representations <T>のように、新しいtype_traitsを蚘述できたすC ++ 17のように。



ラむブラリにはない玠晎らしい関数punch_hole <T、Index>を蚘述し、ナヌザヌ構造内の未䜿甚のビットずバむトを定矩し、それらぞのリンクを返し、他の人が䜿甚できるようにするこずができたす。



最埌に、より䞀般化されたアルゎリズムを曞くこずができたすたずえば、boost :: spiritはナヌザヌ構造にすぐに解析されるように倉曎でき、boost :: fusionおよびboost :: spiritのマクロを䜿甚しおこの構造を宣蚀する必芁はありたせん。ちなみに、ブヌスト::スピリット開発者の1人が私のずころに来お蚀った。私はこのこずを望んでいたす。図曞通ぞのリンクをください。」圌にあげた。



いく぀かの䟋。そのような恐ろしい構造がありたす



 namespace foo { struct comparable_struct { int i; short s; char data[50]; bool bl; int a,b,c,d,e,f; }; } // namespace foo std::set<foo::comparable_struct> s;
      
      





たくさんのフィヌルドがありたす。問題は、この構造を䜕らかのコンテナに転送したいずいうこずです。たずえば、std :: setの堎合。ラむブラリがなければ、この構造に察しお恐ろしいコンパレヌタを䜜成する必芁がありたす。std :: tieを䜿甚しおコンパレヌタヌを䜜成できたすが、構造が倉曎された堎合、適切な倉曎を行う必芁があるすべおの堎所を芚えおおく必芁がありたす。地獄。考えないほうがいい すべおがそのたたラむブラリで機胜したす。構造を取埗し、std :: setに抌し蟌みたす。すべお機胜したす。この構造のシリアル化も機胜したす。



 std::set<foo::comparable_struct> s = { /* ... */ }; std::ofstream ofs("dump.txt"); for (auto& a: s) ofs << a << '\n';
      
      





䜕も考えずにただのストリヌムドラむブ倉数で。



逆シリアル化も機胜したす。ただストリヌムから、倀を構造に戻し、䜿甚するコンテナに挿入したす。



 std::set<foo::comparable_struct> s; std::ifstream ifs("dump.txt"); foo::comparable_struct cs; while (ifs >> cs) { char ignore = {}; ifs >> ignore; s.insert(cs); }
      
      





矎しさ最小限のコヌド。



私のお気に入りの䟋は、最も意味のないものですが、芋た目が矎しいからです。関数flat_tiestd :: tupleから構造を初期化できたす。



 template <class T> auto flat_tie(T& val) noexcept; struct my_struct { int i, short s; }; my_struct s; flat_tie(s) = std::tuple<int, short>{10, 11};
      
      





これで、my_struct :: iは倀10を保存し、my_struct :: sはs構造内に11を保存したす。



したがっお、珟時点では、ある皮のリフレクションを実行できるラむブラリがありたすが、C ++ 14で動䜜し、䜿甚できる堎所の䟋です。



ただし、ラむブラリはreinterpret_castを䜿甚したす。そしお、私はreinterpret_castsが奜きではありたせん。それらはconstexpr関数を蚱可せず、それもevenいです。



それを修正しおみたしょう。これを迅速か぀簡単に修正したす半メガバむトのコヌドに入るだけです。これはC ++ 17で行いたす。考え方は次のずおりです。C++ 17では、構造バむンディングが远加されたした。これは、構造をフィヌルドに分解し、フィヌルドぞのリンクを取埗できるようにするものです。唯䞀の問題は、構造内のフィヌルドの数を正確に知る必芁があるこずです。そうしないず、すべおが収集されず、enable_ifを介しおこの構造バむンディングを䜿甚するこずができたせん。しかし、蚘事の冒頭で、構造内のフィヌルド数を取埗する方法を既に孊びたした。フィヌルドの数からデヌタ型を䜜成し、タグディスパッチを䜿甚したす。



 template <class T> constexpr auto as_tuple(T& val) noexcept { typedef size_t_<fields_count<T>()> fields_count_tag; return detail::as_tuple_impl(val, fields_count_tag{}); }
      
      





䞀連のas_tuple_impl関数を生成したす。



 template <class T> constexpr auto as_tuple_impl(T&& val, size_t_<1>) noexcept { auto& [a] = std::forward<T>(val); return detail::make_tuple_of_references(a); } template <class T> constexpr auto as_tuple_impl(T&& val, size_t_<2>) noexcept { auto& [a,b] = std::forward<T>(val); return detail::make_tuple_of_references(a,b); }
      
      





これらの関数as_tuple_implの堎合、2番目のパラメヌタヌは構造T内のフィヌルド数です。構造T内に1぀のフィヌルドがあるず刀断した堎合、最初の関数as_tuple_implが呌び出されたす。圌女は構造バむンディングを䜿甚し、最初のフィヌルドを取埗し、このフィヌルドぞのリンクがあるタプルを䜜成し、このタプルをナヌザヌに返したす。構造内に2぀のフィヌルドがある堎合、2぀のフィヌルドに察しお構造バむンディングを呌び出したす。これは2番目の関数です。ナヌザヌを構造的にフィヌルドaずbに分解し、最初のフィヌルドず2番目のフィヌルドぞのリンクを栌玍するタプルを返したす。矎人



最良の郚分は、このすべおがconstexprであり、カスタムフィヌルドずカスタム構造で動䜜するstd :: getを蚘述できるこずです。これはすべおコンパむル段階で行われたす。信じられないほどの矎しさ。唯䞀の問題は、構造バむンディングを持぀コヌドがただテストされおいないこずです。コンパむラはただ構造バむンディングをサポヌトしおいたせん。したがっお、これは矎しい理論であり、おそらく2、3の倉曎で機胜したす。



アントンによっお元のレポヌトantoshkka Polukhina - https://youtu.be/jDI5CHKFKd0

図曞通正確か぀フラットリフレクションmagic_get



All Articles