C++ 用户定义字面量 User-defined literals
百度百科:
在计算机科学中,字面量(literal)是用于表达源代码中一个固定值的表示法(notation)。
C++ 中,字面量分为以下几种:
- 整型字面量 integer literal
- 浮点型字面量 floating-point literal
- 布尔型字面量 boolean literal
- 字符字面量 character literal
- 字符串字面量 string literal
- 用户定义字面量 user-defined literal
本文暂只对用户定义字面量进行讨论。
用户定义字面量 User-defined literal
(从 C++11 开始启用)
C++ 允许我们定义一个用户定义后缀(user-defined suffix)来把原生的整型、浮点型、字符、字符串 4 种字面量转换为我们自己需要的形式,这个形式可以是原生数据类型,也可以是自定义类。
具体地,字面量会自动调用一个函数来转换它的类型。由用户定义的字面量调用的函数称为字面量运算符(或者,如果它是模板,则称为字面量运算符模板)。
定义语法
字面量运算符的函数名以 operator"" 开头,后面紧跟用户定义后缀。
返回值类型 operator"" 自定义后缀 (参数);
如果字面量运算符是一个模板,它必须有一个空的参数列表。并且只能有一个模板参数,这个模板参数必须是一个元素类型为 char 的非类型模板参数包(a non-type template parameter pack with element type char)。在这种情况下它被称为数字字面量运算符模板(numeric literal operator template)。
template  返回值类型 operator"" 自定义后缀 ();
 注意,其中的自定义后缀 必须以下划线 _ 开头,并符合标识符命名规范。
一些事实
其实“以下划线开头”并非是从语法上强制性的。
如果你坚持不以下划线开头,那么编译器会给你一个警告:
[Warning] literal operator suffixes not preceded by '_' are reserved for future standardization [-Wliteral-suffix]
但是毕竟只是一个“警告”。
另外,由于字面量的特殊语法结构,自己定义的这个后缀其实可以同时是 C++ 关键字而不产生冲突,这是合法的。
如 C++ 标准库 if 来把整形和浮点型转换为 complex :
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wliteral-suffix"
  constexpr std::complex
  operator""if(long double __num)
  { return std::complex{0.0F, static_cast(__num)}; }
  
  constexpr std::complex
  operator""if(unsigned long long __num)
  { return std::complex{0.0F, static_cast(__num)}; }
  
#pragma GCC diagnostic pop
      (同时使用了预编译指令来忽略刚才所说的警告)
关于参数列表:
对于字面量运算符的参数, C++ 有语法上严格的规定。参数列表仅允许以下几种类型:
| Parameter Lists | Details | ||
|---|---|---|---|
| 1 | ( const char *) | 原始字面量运算符(raw literal operators),用于整数和浮点用户定义字面量的后备方式 | |
| 2 | ( unsigned long long int) | 用户定义整型字面量运算符的首选方式 | |
| 3 | ( long double) | 用户定义浮点型字面量运算符的首选方式 | |
| 4 | ( char) | 通过用户定义字符型字面量调用 (user-defined character literals) | |
| 5 | ( wchar_t) | ||
| 6 | ( char8_t) | ||
| 7 | ( char16_t) | ||
| 8 | ( char32_t) | ||
| 9 | ( const char *,std::size_t) | 通过用户定义字符串字面量调用 (user-defined string literals) 其中第二个参数被自动传入字符串长度 | |
| 10 | ( const wchar_t *,std::size_t) | ||
| 11 | ( const char8_t *,std::size_t) | ||
| 12 | ( const char16_t *,std::size_t) | ||
| 13 | ( const char32_t *,std::size_t) | ||
| 备注 | 注意 char8_t从 C++20 开始启用。不允许使用默认参数。 不允许使用 C 语言链接。 | ||
除了上述限制之外,字面量运算符(和字面量运算符模板)是普通函数(和函数模板),它们可以声明为 inline 或 constexpr ,它们可能具有内部或外部链接,它们可以显式调用,它们的地址可以被获取等。
注意
由于最大吞噬规则,以 e 和 E (C++17 起还有 p 和 P) 结束的用户定义整数和浮点字面量,在后随运算符 + 或 - 时,必须在源码中以空白符或括号与运算符分隔:
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 
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),这导致编译失败。
最大吞噬规则
最大吞噬规则指的是编译器在处理源文件时,翻译阶段中解析预处理记号的一种特性。
如果一个给定字符前的输入已被解析为预处理记号,下一个预处理记号通常会由能构成预处理记号的最长字符序列构成,即使这样处理会导致后续分析失败。这常被称为最大吞噬。
比如经典的 i+++++i 会被解析为 i++ ++ +i 而报错,而不是人们所想的 i++ + ++i 。
示例
#include 
using std::cout;
namespace test
{
	struct My_type
	{
		double value;
		void print() const { cout << "print value: " << value << '\n'; }
	};
	namespace literal
	{
		  // 输出 整型 和 浮点型
		void operator"" _output(const char* num)
		{
			cout << "output: " << num << '\n';
		}
		  // 类型转换
		inline My_type operator"" _to_My_type(unsigned long long num)
		{
			My_type tmp;
			tmp.value = num;
			return tmp;
		}
		  // 四舍五入
		constexpr int operator"" _round(long double num)
		{
			return int( num + 0.5 );
		}
		  // 获取字符的 ASCII 码
		constexpr int operator"" _get_ascii(char c)
		{
			return int(c);
		}
		  // 获取字符串长度
		constexpr size_t operator"" _get_len(const char* s, size_t len)
		{
			return len;
		}
	}
}
  // 一般来说,字面量容易重名,所以常常放在命名空间里
using namespace test::literal;
int main()
{
	114514_output;
	3.14_output;
	2022_to_My_type .print();  // 注意这里的空格
	cout <<  "round: " <<   9.86_round;
	cout <<   "\n\t"   <<   9.39_round   << '\n';
	cout <<  "ascii: " << 'A'_get_ascii  << '\n';
	cout << "strlen: " << "abcd"_get_len << '\n';
	return 0;
}
 输出结果
C++ 标准库对用户定义字面量的应用
此处仅举部分例子。
| 字面量运算符 | 类型 | 作用 | 起始版本 | 备注 | 
|---|---|---|---|---|
| operator""if | std::complex字面量 | 表示纯虚数 | C++14 | 定义于内联命名空间 std::literals::complex_literals | 
| operator""i | ||||
| operator""il | ||||
| operator""h | std::chrono::duration字面量 | 表示小时 | C++14 | 定义于内联命名空间 std::literals::chrono_literals | 
| operator""min | 表示分钟 | |||
| operator""s | 表示秒 | |||
| operator""ms | 表示毫秒 | |||
| operator""us | 表示微秒 | |||
| operator""ns | 表示纳秒 | |||
| operator""y | std::chrono::year字面量 | 表示特定年 | C++20 | |
| operator""d | std::chrono::day字面量 | 表示月内日期 | ||
| operator""s | std::basic_string字面量 | 转换字符数组字面量为 std::basic_string | C++14 | 定义于内联命名空间 std::literals::string_literals | 
| operator""sv | std::basic_string_view字面量 | 创建一个字符数组字面量的字符串视图 | C++17 | 定义于内联命名空间 std::literals::string_view_literals | 
显然 std 没有遵守以下划线开头的命名规范
参考:
1.https://en.cppreference.com/w/cpp/language/user_literal
2.https://en.cppreference.com/w/cpp/language/translation_phases#.E6.9C.80.E5.A4.A7.E5.90.9E.E5.99.AC