文字列のリテラル演算子テンプレート

C ++ 11標準では、ユーザーリテラルなどを言語に導入しました[1] 。 具体的には、 「」演算子を定義するための1ダースのオプションで、わずかな構文糖を追加します。ただし、テンプレートバリアントの1つを除きます。



template <char...> type operator "" _op();
      
      





このオプションは、接尾辞を使用して関数を呼び出すための代替オプションを提供するだけでなく、コンパイル段階で渡された引数を解析するための独自のルールを定義できるため、コンパイラの機能を拡張できるという点で異なります。



例:



 auto x = 10001000100011001001001010001000_b;
      
      





ただし、標準の開発にはわずかな欠落がありました。ユーザーリテラルのテンプレートバージョンでは、文字ごとに解析されるという事実にもかかわらず、数値引数のみを使用できます。

もちろん、そのような省略は気付かれることはありませんでした。C++ 14標準承認の段階で、文字列引数の解決策が提案されました[2]



 template <typename CharT, CharT ...String> type operator "" _op();
      
      





間もなくGCC [3]およびclang(GNU拡張)コンパイラに実装されました。 ただし、C ++ 14標準の最終版は作成しませんでした。 しかし、絶望しないでください、C ++ 17が私たちを喜ばせるという希望があります。 それまでの間、新しいタイプのユーザーリテラルを適用する方法を見てみましょう。



メタ文字列テンプレートを定義します。



 template<char ... Chars> struct str { static constexpr const char value[sizeof...(Chars)+1] = {Chars...,'\0'}; static constexpr int size = sizeof...(Chars); }; template<char ... Chars> constexpr const char str<Chars...>::value[sizeof...(Chars)+1];
      
      





メタ文字列リテラルジェネレーターを定義します。



 template<typename CharT, CharT ...String> constexpr str<String...> operator"" _s() { return str<String...>(); }
      
      





型をキーとして、データ構造のマップのようなテンプレートを作成しましょう。



 template<class Type, class Key> struct field { using key = Key; using type = Type; type value; }; template<class,class,int N=0> struct field_by_type; template<class Key, class Type, class ... Tail, int N> struct field_by_type<Key, std::tuple<field<Type,Key>,Tail...>, N> { static constexpr int value = N; }; template<class Key, class Head, class ... Tail, int N> struct field_by_type<Key, std::tuple<Head,Tail...>, N> : field_by_type<Key,std::tuple<Tail...>,N+1> {}; template<class ... Fields> struct record { using tuple_type = std::tuple<Fields...>; template<class Key> typename std::tuple_element<field_by_type<Key,tuple_type>::value,tuple_type>::type::type& operator[](Key) { return std::get<field_by_type<Key,tuple_type>::value>(data).value; } template<class Key> const typename std::tuple_element<field_by_type<Key,tuple_type>::value,tuple_type>::type::type& operator[](Key) const { return std::get<field_by_type<Key,tuple_type>::value>(data).value; } tuple_type data; };
      
      





キータイプとしてメタ文字列を使用するため、いくつかのI / Oを追加します。



 template<class Type, class Key> std::ostream& operator<< (std::ostream& os, const field<Type,Key> f){ os << Key::value << " = " << f.value << "\n"; return os; } template<int I, typename... Ts> struct print_tuple { std::ostream& operator() (std::ostream& os, const std::tuple<Ts...>& t) { os << std::get<sizeof...(Ts)-I>(t); return print_tuple<I - 1, Ts...>{}(os,t); } }; template<typename... Ts> struct print_tuple<0, Ts...> { std::ostream& operator() (std::ostream& os, const std::tuple<Ts...>& t) { return os; } }; template<class ... Fields> std::ostream& operator<< (std::ostream& os, const record<Fields...>& r) { os << "{\n"; print_tuple<sizeof...(Fields),Fields...>{}(os,r.data); os << "}"; return os; }
      
      





さて、今の例自体:

 using Person = record< field<int, decltype("id"_s)>, field<std::string, decltype("first_name"_s)>, field<std::string, decltype("last_name"_s)> >; int main(){ Person p; p["id"_s] = 10; p["first_name"_s] = "John"; p["last_name"_s] = "Smith"; std::cout << p << "\n"; }
      
      





新しい関数を追加して継承することもできます。

 class Person : public record< field<int, decltype("id"_s)>, field<std::string, decltype("first_name"_s)>, field<std::string, decltype("last_name"_s)> > { public: void set_name(const std::string& f,const std::string& l) { (*this)["first_name"_s] = f; (*this)["last_name"_s] = l; }; }; int main(){ Person p; p["id"_s] = 10; p.set_name("John","Smith"); std::cout << p << "\n"; }
      
      





結果のオブジェクトは、指定されたキーによってフィールドのタイプを静的に推測します。 また、無効なキーを使用すると、コンパイルエラーが生成されるだけでなく、clangコンパイラに入力することもできます。







参照資料



  1. ユーザー定義リテラル (cppreference)
  2. N3599文字列のリテラル演算子テンプレート (Richard Smith)
  3. [C ++ 1y] n3599のサポート-C ++ 1yの文字列のリテラル演算子テンプレート (GCCプロジェクト)



All Articles