用户定义字面量 (C++11 起)
通过定义用户定义的后缀,允许整数、浮点数、字符及字符串字面量产生用户定义类型的对象。
语法
用户定义字面量是下列形式的表达式之一:
十进制字面量 用户定义后缀 | (1) | ||||||||
八进制字面量 用户定义后缀 | (2) | ||||||||
十六进制字面量 用户定义后缀 | (3) | ||||||||
二进制字面量 用户定义后缀 | (4) | ||||||||
分数常量 指数部分 (可选) 用户定义后缀 | (5) | ||||||||
数字序列 指数部分 用户定义后缀 | (6) | ||||||||
字符字面量 用户定义后缀 | (7) | ||||||||
字符串字面量 用户定义后缀 | (8) | ||||||||
十进制字面量 | - | 与整数字面量中相同,非零的十进制数位后随零或多个十进制数位 |
八进制字面量 | - | 与整数字面量中相同,零后随零或多个八进制数位 |
十六进制字面量 | - | 与整数字面量中相同,0x 或 0X 后随一个或多个十六进制数位
|
二进制字面量 | - | 与整数字面量中相同,0b 或 0B 后随一或多个二进制数位
|
数字序列 | - | 与浮点字面量中相同,一个十进制数字序列 |
分数常量 | - | 与浮点字面量中相同,要么是一个后随小数点的 数字序列(123.),要么是一个可选的 数字序列 后随小数点和另一个 数字序列(1.0 或 .12) |
指数部分 | - | 与浮点字面量中相同,字母 e 或字母 E 后随可选的正负号,后随 数字序列
|
字符字面量 | - | 与字符字面量中相同 |
字符串字面量 | - | 与字符串字面量中相同,包括原始字符串字面量 |
用户定义后缀 | - | 标识符,由字面量运算符或字面量运算符模板声明引入(见下文) |
在整数和浮点数字序列中,可以在任何两个数位间插入分隔符 |
(C++14 起) |
如果一个记号同时匹配用户定义字面量的语法和常规字面量的语法,那么它被假定为常规字面量(即不可能重载 123LL 中的 LL)。
当编译器遇到一个带有 用户定义后缀 X
的用户定义字面量时,它进行无限定名字查找,寻找名为 operator ""X
的函数。如果找不到任何声明,那么程序非良构。否则,
a) 如果重载集包含带非类型模板形参的字符串字面量运算符模板,且 str 对它是良构的模板实参,则用户定义字面量表达式被当作函数调用 operator ""X<str>();
|
(C++20 起) |
long double operator ""_w(long double); std::string operator ""_w(const char16_t*, size_t); unsigned operator ""_w(const char*); int main() { 1.2_w; // 调用 operator ""_w(1.2L) u"one"_w; // 调用 operator ""_w(u"one", 3) 12_w; // 调用 operator ""_w("12") "two"_w; // 错误:没有适用的字面量运算符 }
当在翻译阶段 6 中发生字符串字面量的拼接时,用户定义字符串字面量也会被拼接,且其 用户定义后缀 在拼接时被忽略,但所有被拼接的字面量中只可以出现一个后缀:
int main() { L"A" "B" "C"_x; // OK:同 L"ABC"_x "P"_x "Q" "R"_y; // 错误:两个不同的用户定义后缀(_x 与 _y) }
字面量运算符
用户定义字面量所调用的函数被称为字面量运算符(如果它是模板,那么它被称为字面量运算符模板)。它的声明恰如任何其他命名空间作用域的函数或函数模板一样(它可以是友元函数、函数模板的显式实例化或特化,或通过 using 声明引入),但有下列限制:
此函数的名称可拥有两种形式之一:
operator "" 标识符
|
(1) | (弃用) | |||||||
operator 用户定义字符串字面量
|
(2) | ||||||||
标识符 | - | 作为用户定义字面量所用且会调用此函数的 用户定义后缀 的标识符 |
用户定义字符串字面量 | - | 字符序列 "" 后不带空格,后随将作为 用户定义后缀 的字符序列
|
用户定义后缀 必须以下划线 _
开始:不以下划线开始的后缀为标准库提供的字面量运算符所保留。
用户定义后缀 也不能包含双下划线 __
:此类后缀也被保留。
如果字面量运算符是模板,那么它必须有空形参列表,并且只能有一个模板形参,模板形参必须是元素类型是 char 的非类型模板形参包(此时称之为数值字面量运算符模板):
template<char...> double operator ""_x();
或类类型的非类型模板形参(此时称之为字符串字面量运算符模板): struct A { constexpr A(const char*); }; template<A a> A operator ""_a(); |
(C++20 起) |
字面量运算符仅允许下列形参列表:
( const char* )
|
(1) | ||||||||
( unsigned long long int )
|
(2) | ||||||||
( long double )
|
(3) | ||||||||
( char )
|
(4) | ||||||||
( wchar_t )
|
(5) | ||||||||
( char8_t )
|
(6) | (C++20 起) | |||||||
( char16_t )
|
(7) | ||||||||
( char32_t )
|
(8) | ||||||||
( const char*, std::size_t )
|
(9) | ||||||||
( const wchar_t*, std::size_t )
|
(10) | ||||||||
( const char8_t*, std::size_t )
|
(11) | (C++20 起) | |||||||
( const char16_t*, std::size_t )
|
(12) | ||||||||
( const char32_t*, std::size_t )
|
(13) | ||||||||
不能有默认实参。
不允许 C 语言链接。
除了上述限制外,字面量运算符和字面量运算符模板都是普通的函数(和函数模板),它们可声明为 inline 或 constexpr,它们可拥有内部或外部链接,它们可显式调用,可被取地址,等等。
#include <string> void operator ""_km(long double); // OK ,将为 1.0_km 所调用 void operator "" _km(long double); // 同上,弃用 std::string operator ""_i18n(const char*, std::size_t); // OK template <char...> double operator ""_π(); // OK float operator ""_e(const char*); // OK // 错误:后缀必须以下划线开始 float operator ""Z(const char*); // 错误:所有以下划线后随大写字母开始的名称受到保留(注意 "" 和 _ 之间的空格) double operator"" _Z(long double); // OK:注意 "" 和 _ 之间没有空格 double operator""_Z(long double); // OK:可以重载字面量运算符 double operator ""_Z(const char* args); int main() {}
注解
自从引入用户定义字面量之后,使用定宽整数类型格式化宏常量且未在前导字符串字面量后加空格的情况变为非法:std::printf("%"PRId64"\n",INT64_MIN); 必须替换成 std::printf("%" PRId64"\n",INT64_MIN);
由于最大吞噬规则,以 p
、P
、 (C++17 起)e
和 E
结束的用户定义整数和浮点字面量,在后随运算符 +
或 -
时,必须在源码中以空白符或括号与运算符分隔:
long double operator""_E(long double); long double operator""_a(long double); int operator""_p(unsigned long long); auto x = 1.0_E+2.0; // 错误 auto y = 1.0_a+2.0; // OK auto z = 1.0_E +2.0; // OK auto q = (1.0_E)+2.0; // OK auto w = 1_p+2; // 错误 auto u = 1_p +2; // OK
同样的规则适用于整数或浮点用户定义字面量后的点运算符:
#include <chrono> using namespace std::literals; auto a = 4s.count(); // 错误 auto b = 4s .count(); // OK auto c = (4s).count(); // OK
否则会组成单个非法预处理数字记号(例如 1.0_E+2.0 或 4s.count),这会导致编译失败。
示例
#include <algorithm> #include <cstddef> #include <iostream> #include <numbers> #include <string> // 用作从度(输入参数)转换为弧度(返回输出) constexpr long double operator""_deg_to_rad(long double deg) { long double radians = deg * std::numbers::pi_v<long double> / 180; return radians; } // 用作自定义类型 struct mytype { unsigned long long m; }; constexpr mytype operator""_mytype(unsigned long long n) { return mytype{n}; } // 用作副作用 void operator""_print(const char* str) { std::cout << str << '\n'; } #if __cpp_nontype_template_args < 201911 std::string operator""_x2(const char* str, std::size_t) { return std::string{str} + str; } #else // C++20 字符串字面量运算符模板 template<std::size_t N> struct DoubleString { char p[N * 2 - 1]{}; constexpr DoubleString(char const(&pp)[N]) { std::ranges::copy(pp, p); std::ranges::copy(pp, p + N - 1); }; }; template<DoubleString A> constexpr auto operator""_x2() { return A.p; } #endif // C++20 int main() { double x_rad = 90.0_deg_to_rad; std::cout << std::fixed << x_rad << '\n'; mytype y = 123_mytype; std::cout << y.m << '\n'; 0x123ABC_print; std::cout << "abc"_x2 << '\n'; }
输出:
1.570796 123 0x123ABC abcabc
标准库
标准库中定义了下列字面量运算符:
在内联命名空间
std::literals::complex_literals 定义 | |
表示纯虚数的 std::complex 字面量 (函数) | |
在内联命名空间
std::literals::chrono_literals 定义 | |
(C++14) |
表示小时的 std::chrono::duration 字面量 (函数) |
(C++14) |
表示分钟的 std::chrono::duration 字面量 (函数) |
(C++14) |
表示秒的 std::chrono::duration 字面量 (函数) |
(C++14) |
表示毫秒的 std::chrono::duration 字面量 (函数) |
(C++14) |
表示微秒的 std::chrono::duration 字面量 (函数) |
(C++14) |
表示纳秒的 std::chrono::duration 字面量 (函数) |
(C++20) |
表示特定年的 std::chrono::year 字面量 (函数) |
(C++20) |
表示月内日期的 std::chrono::day 字面量 (函数) |
在内联命名空间
std::literals::string_literals 定义 | |
(C++14) |
转换字符数组字面量为 basic_string (函数) |
在内联命名空间
std::literals::string_view_literals 定义 | |
(C++17) |
创建一个字符数组字面量的字符串视图 (函数) |
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
缺陷报告 | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 1473 | C++11 | 字面量运算符声明中曾要求 "" 与 用户定义后缀 间的空格 | 不再强制要求 |
CWG 1479 | C++11 | 字面量运算符可以有默认实参 | 不能有默认实参 |
CWG 2521 | C++11 | operator"" _Bq 使用了保留的标识 符 _Bq ,因此它非良构(不要求诊断)
|
弃用 "" 和 用户定义后缀 之间 有空格的字面量运算符语法 |