Определенные пользователем литералы
Существует шесть основных категорий литералов в C++: целое число, символ, плавающая точка, строка, логическое значение и указатель. Начиная с C++11 можно определить собственные литералы на основе этих категорий, чтобы обеспечить синтаксические сочетания клавиш для распространенных идиом и повысить безопасность типов. Например, предположим, что у вас есть Distance
класс. Можно определить литерал для километров и еще один для миль, и поощрять пользователя быть явным в единицах измерения путем написания: auto d = 42.0_km
или auto d = 42.0_mi
. Нет преимуществ производительности или недостатка для определяемых пользователем литералов; они в основном для удобства или для вычета типов во время компиляции. В стандартной библиотеке есть определяемые пользователем литералы для , для std::string
std::complex
и для единиц времени и длительности операций в заголовке <хрона>:
Distance d = 36.0_mi + 42.0_km; // Custom UDL (see below)
std::string str = "hello"s + "World"s; // Standard Library <string> UDL
complex<double> num =
(2.0 + 3.01i) * (5.0 + 4.3i); // Standard Library <complex> UDL
auto duration = 15ms + 42h; // Standard Library <chrono> UDLs
Сигнатуры определяемых пользователем литеральных операторов
Вы реализуете определяемый пользователем литерал, определяя оператор "" в области пространства имен с одной из следующих форм:
ReturnType operator "" _a(unsigned long long int); // Literal operator for user-defined INTEGRAL literal
ReturnType operator "" _b(long double); // Literal operator for user-defined FLOATING literal
ReturnType operator "" _c(char); // Literal operator for user-defined CHARACTER literal
ReturnType operator "" _d(wchar_t); // Literal operator for user-defined CHARACTER literal
ReturnType operator "" _e(char16_t); // Literal operator for user-defined CHARACTER literal
ReturnType operator "" _f(char32_t); // Literal operator for user-defined CHARACTER literal
ReturnType operator "" _g(const char*, size_t); // Literal operator for user-defined STRING literal
ReturnType operator "" _h(const wchar_t*, size_t); // Literal operator for user-defined STRING literal
ReturnType operator "" _i(const char16_t*, size_t); // Literal operator for user-defined STRING literal
ReturnType operator "" _g(const char32_t*, size_t); // Literal operator for user-defined STRING literal
ReturnType operator "" _r(const char*); // Raw literal operator
template<char...> ReturnType operator "" _t(); // Literal operator template
Имена операторов, использованные в предыдущем примере, можно заменить на любые другие. Обязательным является лишь символ подчеркивания в начале. (Только стандартная библиотека может определять литералы без подчеркивания.) Тип возвращаемого значения — это способ настройки преобразования или других операций, выполняемых литералом. Кроме того, все эти операторы могут быть определены как constexpr
.
Литералы с обработкой
В исходном коде любой литерал, определенный пользователем или нет, по сути, является последовательностью буквенно-цифровых символов, таких как 101
, или 54.7
, или "hello"
true
. Компилятор интерпретирует последовательность как целое число, строку float, const char* и т. д. Определяемый пользователем литерал, который принимает в качестве входных данных любой тип компилятора, назначенный значению литерала, неофициально называется подготовленным литералом. Все операторы, описанные выше, за исключением _r
и _t
, являются литералами с обработкой. Например, литерал 42.0_km
будет привязан к оператору с именем _km, имеющему сигнатуру похожую на _b, а литерал 42_km
будет привязан к оператору с сигнатурой похожей на _a.
В следующем примере показано, как определяемые пользователем литералы могут потребовать от пользователей явно указывать входные данные. Чтобы создать Distance
, пользователь должен явно указать километры или мили с помощью соответствующего пользовательского литерала. Вы можете добиться того же результата другими способами, но определяемые пользователем литералы менее подробны, чем альтернативные.
// UDL_Distance.cpp
#include <iostream>
#include <string>
struct Distance
{
private:
explicit Distance(long double val) : kilometers(val)
{}
friend Distance operator"" _km(long double val);
friend Distance operator"" _mi(long double val);
long double kilometers{ 0 };
public:
const static long double km_per_mile;
long double get_kilometers() { return kilometers; }
Distance operator+(Distance other)
{
return Distance(get_kilometers() + other.get_kilometers());
}
};
const long double Distance::km_per_mile = 1.609344L;
Distance operator"" _km(long double val)
{
return Distance(val);
}
Distance operator"" _mi(long double val)
{
return Distance(val * Distance::km_per_mile);
}
int main()
{
// Must have a decimal point to bind to the operator we defined!
Distance d{ 402.0_km }; // construct using kilometers
std::cout << "Kilometers in d: " << d.get_kilometers() << std::endl; // 402
Distance d2{ 402.0_mi }; // construct using miles
std::cout << "Kilometers in d2: " << d2.get_kilometers() << std::endl; //646.956
// add distances constructed with different units
Distance d3 = 36.0_mi + 42.0_km;
std::cout << "d3 value = " << d3.get_kilometers() << std::endl; // 99.9364
// Distance d4(90.0); // error constructor not accessible
std::string s;
std::getline(std::cin, s);
return 0;
}
Число литерала должно использовать десятичное значение. В противном случае число будет интерпретировано как целое число, и тип не будет совместим с оператором. Для входных данных с плавающей запятой тип должен быть long double
, а для целочисленных типов он должен быть long long
.
Необработанные литералы
В необработанном пользовательском литерале оператор, который определяется, принимает литерал в виде последовательности значений char. Вы можете интерпретировать эту последовательность как число или строку или другой тип. В списке операторов, приведенном ранее на этой странице, есть операторы, _r
и _t
, которые можно использовать для определения необработанных литералов:
ReturnType operator "" _r(const char*); // Raw literal operator
template<char...> ReturnType operator "" _t(); // Literal operator template
Необработанные литералы можно использовать для предоставления пользовательской интерпретации входной последовательности, отличной от нормального поведения компилятора. Например, вы можете определить литерал, преобразующий последовательность 4.75987
в пользовательский тип десятичного числа вместо типа с плавающей запятой по стандарту IEEE 754. Необработанные литералы, такие как приготовленные литералы, также можно использовать для проверки входных последовательностей во время компиляции.
Пример. Ограничения необработанных литерала
Оператор необработанного литерала и шаблон оператора литерала работают только с пользовательскими литералами, имеющими целочисленный тип или тип числа с плавающей запятой, как показано в следующем примере.
#include <cstddef>
#include <cstdio>
// Literal operator for user-defined INTEGRAL literal
void operator "" _dump(unsigned long long int lit)
{
printf("operator \"\" _dump(unsigned long long int) : ===>%llu<===\n", lit);
};
// Literal operator for user-defined FLOATING literal
void operator "" _dump(long double lit)
{
printf("operator \"\" _dump(long double) : ===>%Lf<===\n", lit);
};
// Literal operator for user-defined CHARACTER literal
void operator "" _dump(char lit)
{
printf("operator \"\" _dump(char) : ===>%c<===\n", lit);
};
void operator "" _dump(wchar_t lit)
{
printf("operator \"\" _dump(wchar_t) : ===>%d<===\n", lit);
};
void operator "" _dump(char16_t lit)
{
printf("operator \"\" _dump(char16_t) : ===>%d<===\n", lit);
};
void operator "" _dump(char32_t lit)
{
printf("operator \"\" _dump(char32_t) : ===>%d<===\n", lit);
};
// Literal operator for user-defined STRING literal
void operator "" _dump(const char* lit, size_t)
{
printf("operator \"\" _dump(const char*, size_t): ===>%s<===\n", lit);
};
void operator "" _dump(const wchar_t* lit, size_t)
{
printf("operator \"\" _dump(const wchar_t*, size_t): ===>%ls<===\n", lit);
};
void operator "" _dump(const char16_t* lit, size_t)
{
printf("operator \"\" _dump(const char16_t*, size_t):\n" );
};
void operator "" _dump(const char32_t* lit, size_t)
{
printf("operator \"\" _dump(const char32_t*, size_t):\n" );
};
// Raw literal operator
void operator "" _dump_raw(const char* lit)
{
printf("operator \"\" _dump_raw(const char*) : ===>%s<===\n", lit);
};
template<char...> void operator "" _dump_template(); // Literal operator template
int main(int argc, const char* argv[])
{
42_dump;
3.1415926_dump;
3.14e+25_dump;
'A'_dump;
L'B'_dump;
u'C'_dump;
U'D'_dump;
"Hello World"_dump;
L"Wide String"_dump;
u8"UTF-8 String"_dump;
u"UTF-16 String"_dump;
U"UTF-32 String"_dump;
42_dump_raw;
3.1415926_dump_raw;
3.14e+25_dump_raw;
// There is no raw literal operator or literal operator template support on these types:
// 'A'_dump_raw;
// L'B'_dump_raw;
// u'C'_dump_raw;
// U'D'_dump_raw;
// "Hello World"_dump_raw;
// L"Wide String"_dump_raw;
// u8"UTF-8 String"_dump_raw;
// u"UTF-16 String"_dump_raw;
// U"UTF-32 String"_dump_raw;
}
operator "" _dump(unsigned long long int) : ===>42<===
operator "" _dump(long double) : ===>3.141593<===
operator "" _dump(long double) : ===>31399999999999998506827776.000000<===
operator "" _dump(char) : ===>A<===
operator "" _dump(wchar_t) : ===>66<===
operator "" _dump(char16_t) : ===>67<===
operator "" _dump(char32_t) : ===>68<===
operator "" _dump(const char*, size_t): ===>Hello World<===
operator "" _dump(const wchar_t*, size_t): ===>Wide String<===
operator "" _dump(const char*, size_t): ===>UTF-8 String<===
operator "" _dump(const char16_t*, size_t):
operator "" _dump(const char32_t*, size_t):
operator "" _dump_raw(const char*) : ===>42<===
operator "" _dump_raw(const char*) : ===>3.1415926<===
operator "" _dump_raw(const char*) : ===>3.14e+25<===