📜 ⬆️ ⬇️

Literal operator templates for strings

The C ++ 11 standard introduced into the language such a thing as custom literals [1] . Specifically, a dozen variants for the definition of the operator "" , which add a little syntactic sugar, all, except for one - the template variant:

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

This variant differs in that it does not simply provide an alternative option for calling a function using a suffix, but allows you to define your own rules for parsing the passed argument at compile time, thereby expanding the capabilities of the compiler.

For example:
')
 auto x = 10001000100011001001001010001000_b; 

However, a small omission was made in the development of the standard - a sample version of the user literal allows you to work only with numeric arguments, despite the fact that they are parsed character-by-character.
Such an omission, of course, could not remain unnoticed, and at the stage of harmonization of the C ++ standard 14 a solution was proposed for string arguments [2]

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

It was soon implemented in the GCC [3] and clang compilers (GNU extension). However, the final edition of the C ++ 14 standard did not fall. However, let's not despair, there is hope that we will be pleased with C ++ 17. For now, let's see how it will be possible to apply a new type of custom literals.

Define the meta-string pattern:

 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]; 

Define our meta-string literal generator:

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

Create a template map-like data structure, with types as keys:

 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; }; 

Since we are going to use meta-strings as key types, add some 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; } 

And now the example itself:
 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"; } 

We can also inherit by adding new features:
 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"; } 

The resulting objects statically deduce the field type by the specified key. And when using an invalid key, they not only generate a compilation error, but they can also paint over the clang compiler:



Links


  1. User-defined literals (cppreference)
  2. N3599 Literal operator templates for strings (Richard Smith)
  3. [C ++ 1y] Support n3599 - Literal operator templates for C ++ 1y (GCC Project)

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


All Articles