[Из песочницы] Literal operator templates for strings19.11.2014 04:48
Стандарт C++11 привнес в язык такую вещь, как пользовательские литералы[1]. Конкретно — дюжину вариантов для определения оператора », добавляющих небольшой синтаксический сахар, всех, за исключением одного — шаблонного варианта:
template type operator » _op ();
Отличается этот вариант тем, что он не просто дает альтернативный вариант вызова функции с помощью суффикса, а позволяет определить свои правила парсинга передаваемого аргумента на этапе компиляции, расширяя тем самым возможности компилятора.Например:
auto x = 10001000100011001001001010001000_b;
Однако при разработке стандарта было допущено небольшое упущение — шаблонный вариант пользовательского литерала позволяет работать только с числовыми аргументами, несмотря на то, что парсинг их осуществляется посимвольно.Такое упущение, конечно же, не могло остаться не замеченным, и на этапе согласования стандарта C++14 было предложено решение для строковых аргументов[2]
template type operator » _op ();
В скором времени было реализовано в компиляторах GCC[3] и clang (GNU extension). Однако в финальную редакцию стандарта C++14 так и не попало. Впрочем, не будем отчаиваться, есть надежда, что нас обрадует C++17. А пока посмотрим, как можно будет применять новый тип пользовательских литералов.Определим шаблон мета-строки:
template
struct str {
static constexpr const char value[sizeof…(Chars)+1] = {Chars…,'\0'};
static constexpr int size = sizeof…(Chars);
};
Определим наш литерал-генератор мета-строки:
template
constexpr str operator» _s ()
{
return str();
}
Создадим шаблон map-like структуры данных, с типами в качестве ключей:
template
struct field {
using key = Key;
using type = Type;
type value;
};
template
struct field_by_type;
template
struct field_by_type, Tail…>, N> {
static constexpr int value = N;
};
template
struct field_by_type, N>: field_by_type, N+1> {};
template
struct record {
using tuple_type = std: tuple;
template
typename std: tuple_element:: value, tuple_type>:: type: type& operator[](Key) {
return std: get:: value>(data).value;
}
template
const typename std: tuple_element:: value, tuple_type>:: type: type& operator[](Key) const {
return std: get:: value>(data).value;
}
tuple_type data;
};
Так как в качестве типов-ключей мы собираемся использовать мета-строки, добавим немного ввода-вывода:
template
std: ostream& operator<< (std::ostream& os, const field f){
os << Key::value << " = " << f.value << "\n";
return os;
}
template
struct print_tuple {
std: ostream& operator () (std: ostream& os, const std: tuple& t) {
os << std::get(t);
return print_tuple{}(os, t);
}
};
template
struct print_tuple<0, Ts...> {
std: ostream& operator () (std: ostream& os, const std: tuple& t) {
return os;
}
};
template
std: ostream& operator<< (std::ostream& os, const record& r) {
os << "{\n";
print_tuple{}(os, r.data);
os << "}";
return os;
}
Ну а теперь и сам пример:
using Person = record<
field,
field,
field
>;
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,
field,
field,
>
{
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:
Ссылки
User-defined literals (cppreference)
N3599 Literal operator templates for strings (Richard Smith)
[C++1y] Support n3599 — Literal operator templates for strings for C++1y (GCC Project)
© Habrahabr.ru