跳到主要内容

用户定义字面量 (自 C++11 起)

通过定义用户自定义后缀,允许整数、浮点数、字符和 字符串字面量 生成用户自定义类型的对象。

语法

用户自定义字面量是以下任一形式的表达式:

1
十进制字面量ud-suffix
2
八进制字面量ud-suffix
3
十六进制字面量ud-suffix
4
二进制字面量ud-suffix
5
分数常量指数部分(可选)ud-suffix
6
数字序列指数部分ud-suffix
7
字符字面量ud-suffix
8
字符串字面量ud-suffix
pub十进制字面量整数字面量 中的相同,非零十进制数字后跟零个或多个十进制数字
pub八进制字面量整数字面量 中的相同,零后跟零个或多个八进制数字
pub十六进制字面量整数字面量 中的相同,0x 或 0X 后跟一个或多个十六进制数字
pub二进制字面量整数字面量 中的相同,0b 或 0B 后跟一个或多个二进制数字
pub数字序列浮点数字面量 中的相同,一系列十进制数字
pub分数常量浮点数字面量 中的相同,要么是后面带点的 数字序列 (123.),要么是可选的 数字序列 后跟一个点和另一个 数字序列
(1.0 or .12)
pub指数部分浮点数字面量 中的相同,字母 e 或字母 E 后跟可选的符号,再后跟 数字序列
pub字符字面量与字符字面量中的相同
pub字符串字面量字符串字面量 中的相同,包括原始 字符串字面量
pubud-suffix一个标识符,由“字面量运算符”或“字面量运算符模板”声明引入 (见下文)
整数浮点数字 序列中,任何两个数字之间都可以有可选的分隔符 ' (自 C++14 起)

如果一个标记匹配用户自定义字面量语法和常规字面量语法,则它被认为是常规字面量(也就是说,不可能重载 123LL 中的 LL)。

当编译器遇到带有 ud-suffix X 的用户自定义字面量时,它会执行 非限定名称查找,查找名称为 operator""X 的函数。如果查找未找到声明,则程序格式错误。否则,

  1. 对于用户自定义整数字面量,
    1. 如果重载集合包含一个参数类型为 unsigned long long 的字面量运算符,则用户自定义字面量表达式被视为函数调用 operator ""X(nULL),其中 n 是去掉 ud-suffix 的字面量;
    2. 否则,重载集合必须包含原始字面量运算符或数值字面量运算符模板中的一个,但不能同时包含。如果重载集合包含原始字面量运算符,则用户自定义字面量表达式被视为函数调用 operator ""X("n")
    3. 否则,如果重载集合包含数值字面量运算符模板,则用户自定义字面量表达式被视为函数调用 operator ""X(>'c1', 'c2', 'c3'..., 'ck'<()),其中 c1..ckn 的各个字符,并且它们都来自 基本字符集
  2. 对于用户自定义浮点数字面量,
    1. 如果重载集合包含一个参数类型为 long double 的字面量运算符,则用户自定义字面量表达式被视为函数调用 operator ""X(fL),其中 f 是去掉 ud-suffix 的字面量;
    2. 否则,重载集合必须包含原始字面量运算符或数值字面量运算符模板中的一个,但不能同时包含。如果重载集合包含原始字面量运算符,则用户自定义字面量表达式被视为函数调用 operator ""X("f")
    3. 否则,如果重载集合包含数值字面量运算符模板,则用户自定义字面量表达式被视为函数调用 operator ""X(>'c1', 'c2', 'c3'..., 'ck'<()),其中 c1..ckf 的各个字符,并且它们都来自 基本字符集
  3. 对于用户自定义字符串字面量,令 str 为去掉 ud-suffix 的字面量
    1. 如果重载集合包含一个具有非类型模板参数的字符串字面量运算符模板,而 str 是该模板参数的合法模板实参,则用户自定义字面量表达式被视为函数调用 operator ""X<str>(); (C++20 起)
    2. 否则,用户自定义字面量表达式被视为函数调用 operator ""X (str, len),其中 len 是字符串字面量的长度,不包括终止的空字符。
  4. 对于用户自定义字符字面量,用户自定义字面量表达式被视为函数调用 operator ""X (ch),其中 ch 是去掉 ud-suffix 的字面量。
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; // calls operator ""_w(1.2L)
u"one"_w; // calls operator ""_w(u"one", 3)
12_w; // calls operator ""_w("12")
"two"_w; // error: no applicable literal operator
}

当在 翻译阶段 6 中发生字符串字面量连接时,用户自定义字符串字面量也会被连接,并且它们的 ud-suffix 在连接时会被忽略,但前提是连接后的字面量上只能有一个后缀。

int main() {
L"A" "B" "C"_x; // OK: same as L"ABC"_x
"P"_x "Q" "R"_y; // error: two different ud-suffixes (_x and _y)
}

字面量运算符

由用户自定义字面量调用的函数称为字面量运算符(如果它是模板,则称为字面量运算符模板)。它就像任何其他 函数函数模板 一样在命名空间作用域中声明(它也可以是友元函数、函数模板的显式实例化或特化,或由 using-declaration 引入),除了以下限制:

此函数的名称可以有两种形式之一:

1operator ""
标识符 (已弃用)
2operator
user-defined-string-literal
pub标识符用于调用此函数的、用户自定义字面量的 ud-suffix 标识符
pubuser-defined-string-literal字符序列 " ",后面紧跟着成为 ud-suffix 的字符序列,中间没有空格
  1. 声明一个字面量运算符。
  2. 声明一个字面量运算符。此语法使得可以使用语言 关键字 和保留标识符作为 ud-suffix,例如,来自头文件 <complex> 的 operator ""if

 
ud-suffix 必须以_下划线开头:不以下划线开头的后缀保留给标准库提供的字面量运算符。也不能包含双下划线__:此类后缀也保留。

如果字面量运算符是模板,它必须具有空的参数列表,并且只能有一个模板参数,该参数必须是元素类型为 char 的非类型模板参数包(在这种情况下,它被称为数值字面量运算符模板

template<char...>
double operator ""_x();

或者是一个类类型的非类型模板参数(在这种情况下,它被称为字符串字面量运算符模板

struct A { constexpr A(const char*); };

template<A a>
A operator ""_a();
 (自 C++23 起)

字面量运算符只允许以下参数列表:

1
( const char* )
2
( unsigned long long int )
3
( long double )
4
( char )
5
( wchar_t )
6
( char8_t ) (自 C++20 起)
7
( char16_t )
8
( char32_t )
9
( const char*, std::size_t )
10
( const wchar_t*, std::size_t )
11
( const char8_t*, std::size_t ) (自 C++20 起)
12
( const char16_t*, std::size_t )
13
( const char32_t*, std::size_t )
    1. 具有此参数列表的字面量运算符是原始字面量运算符,用作整数和浮点数用户自定义字面量的后备(见上文)
    1. 具有这些参数列表的字面量运算符是用户自定义整数字面量的首选字面量运算符
    1. 具有这些参数列表的字面量运算符是用户自定义浮点数字面量的首选字面量运算符
  • 4-8) 具有这些参数列表的字面量运算符由用户自定义字符字面量调用
  • 9-13) 具有这些参数列表的字面量运算符由用户自定义字符串字面量调用

不允许使用默认参数
不允许使用C 语言连接

除了上述限制外,字面量运算符和字面量运算符模板都是正常的函数(和函数模板),它们可以声明为内联或 constexpr,可以具有内部或外部连接,可以显式调用,可以获取它们的地址等等。

#include <string>

void operator ""_km(long double); // OK, will be called for 1.0_km
void operator "" _km(long double); // same as above, deprecated
std::string operator ""_i18n(const char*, std::size_t); // OK

template<char...>
double operator ""(); // OK
float operator ""_e(const char*); // OK

// error: suffix must begin with underscore
float operator ""Z(const char*);

// error: all names that begin with underscore followed by uppercase
// letter are reserved (NOTE: a space between "" and _).
double operator"" _Z(long double);

// OK. NOTE: no space between "" and _.
double operator""_Z(long double);

// OK: literal operators can be overloaded
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; // error
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; // error
auto u = 1_p +2; // OK

整数或浮点数用户自定义字面量后面的点运算符也适用此规则。

#include <chrono>

using namespace std::literals;

auto a = 4s.count(); // Error
auto b = 4s .count(); // OK
auto c = (4s).count(); // OK

否则,会形成一个无效的预处理数字标记(例如,1.0_E+2.04s.count),这会导致编译失败。

特性测试宏标准注释
__cpp_user_defined_literals200809L(C++11)用户定义字面量

示例

#include <algorithm>
#include <cstddef>
#include <iostream>
#include <numbers>
#include <string>

// used as conversion from degrees (input param) to radians (returned output)
constexpr long double operator""_deg_to_rad(long double deg)
{
long double radians = deg * std::numbers::pi_v<long double> / 180;
return radians;
}

// used with custom type
struct mytype
{
unsigned long long m;
};

constexpr mytype operator""_mytype(unsigned long long n)
{
return mytype{n};
}

// used for side-effects
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 string literal operator template

template<std::size_t N>
struct DoubleString
{
char p[N + N - 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 中
puboperator""if
operator""i
operator""il
 (C++14 起)
一个 std::complex 字面量,表示纯虚数
定义在内联命名空间 std::literals::chrono_literals 中
puboperator""h (C++14 起)一个 std::chrono::duration 字面量,表示小时
puboperator""min (C++14 起)一个 std::chrono::duration 字面量,表示分钟
puboperator""s (C++14 起)一个 std::chrono::duration 字面量,表示秒
puboperator""ms (C++14 起)一个 std::chrono::duration 字面量,表示毫秒
puboperator""us (C++14 起)一个 std::chrono::duration 字面量,表示微秒
puboperator""ns (C++14 起)一个 std::chrono::duration 字面量,表示纳秒
puboperator""y (C++20 起)一个 std::chrono::year 字面量,表示特定年份
puboperator""d (C++20 起)一个 std::chrono::day 字面量,表示月份中的某一天
定义在内联命名空间 std::literals::string_literals 中
puboperator""s (C++14 起)将字符数组字面量转换为 basic_string
定义在内联命名空间 std::literals::string_view_literals 中
puboperator""sv (C++17 起)创建字符数组字面量的 string_view

缺陷报告

以下改变行为的缺陷报告已追溯应用于先前发布的 C++ 标准。

DR应用于发布时的行为正确行为
CWG 1473(C++11)在声明字面量运算符时," "ud-suffix 之间的空格是必需的已改为可选
CWG 1479(C++11)字面量运算符可以有默认参数已禁止
CWG 2521(C++11)operator" " _Bq 格式错误(不要求诊断),因为它使用了保留标识符 _Bq已弃用带空格的字面量运算符声明语法(" "ud-suffix 之间)

用户定义字面量 (自 C++11 起)

通过定义用户自定义后缀,允许整数、浮点数、字符和 字符串字面量 生成用户自定义类型的对象。

语法

用户自定义字面量是以下任一形式的表达式:

1
十进制字面量ud-suffix
2
八进制字面量ud-suffix
3
十六进制字面量ud-suffix
4
二进制字面量ud-suffix
5
分数常量指数部分(可选)ud-suffix
6
数字序列指数部分ud-suffix
7
字符字面量ud-suffix
8
字符串字面量ud-suffix
pub十进制字面量整数字面量 中的相同,非零十进制数字后跟零个或多个十进制数字
pub八进制字面量整数字面量 中的相同,零后跟零个或多个八进制数字
pub十六进制字面量整数字面量 中的相同,0x 或 0X 后跟一个或多个十六进制数字
pub二进制字面量整数字面量 中的相同,0b 或 0B 后跟一个或多个二进制数字
pub数字序列浮点数字面量 中的相同,一系列十进制数字
pub分数常量浮点数字面量 中的相同,要么是后面带点的 数字序列 (123.),要么是可选的 数字序列 后跟一个点和另一个 数字序列
(1.0 or .12)
pub指数部分浮点数字面量 中的相同,字母 e 或字母 E 后跟可选的符号,再后跟 数字序列
pub字符字面量与字符字面量中的相同
pub字符串字面量字符串字面量 中的相同,包括原始 字符串字面量
pubud-suffix一个标识符,由“字面量运算符”或“字面量运算符模板”声明引入 (见下文)
整数浮点数字 序列中,任何两个数字之间都可以有可选的分隔符 ' (自 C++14 起)

如果一个标记匹配用户自定义字面量语法和常规字面量语法,则它被认为是常规字面量(也就是说,不可能重载 123LL 中的 LL)。

当编译器遇到带有 ud-suffix X 的用户自定义字面量时,它会执行 非限定名称查找,查找名称为 operator""X 的函数。如果查找未找到声明,则程序格式错误。否则,

  1. 对于用户自定义整数字面量,
    1. 如果重载集合包含一个参数类型为 unsigned long long 的字面量运算符,则用户自定义字面量表达式被视为函数调用 operator ""X(nULL),其中 n 是去掉 ud-suffix 的字面量;
    2. 否则,重载集合必须包含原始字面量运算符或数值字面量运算符模板中的一个,但不能同时包含。如果重载集合包含原始字面量运算符,则用户自定义字面量表达式被视为函数调用 operator ""X("n")
    3. 否则,如果重载集合包含数值字面量运算符模板,则用户自定义字面量表达式被视为函数调用 operator ""X(>'c1', 'c2', 'c3'..., 'ck'<()),其中 c1..ckn 的各个字符,并且它们都来自 基本字符集
  2. 对于用户自定义浮点数字面量,
    1. 如果重载集合包含一个参数类型为 long double 的字面量运算符,则用户自定义字面量表达式被视为函数调用 operator ""X(fL),其中 f 是去掉 ud-suffix 的字面量;
    2. 否则,重载集合必须包含原始字面量运算符或数值字面量运算符模板中的一个,但不能同时包含。如果重载集合包含原始字面量运算符,则用户自定义字面量表达式被视为函数调用 operator ""X("f")
    3. 否则,如果重载集合包含数值字面量运算符模板,则用户自定义字面量表达式被视为函数调用 operator ""X(>'c1', 'c2', 'c3'..., 'ck'<()),其中 c1..ckf 的各个字符,并且它们都来自 基本字符集
  3. 对于用户自定义字符串字面量,令 str 为去掉 ud-suffix 的字面量
    1. 如果重载集合包含一个具有非类型模板参数的字符串字面量运算符模板,而 str 是该模板参数的合法模板实参,则用户自定义字面量表达式被视为函数调用 operator ""X<str>(); (C++20 起)
    2. 否则,用户自定义字面量表达式被视为函数调用 operator ""X (str, len),其中 len 是字符串字面量的长度,不包括终止的空字符。
  4. 对于用户自定义字符字面量,用户自定义字面量表达式被视为函数调用 operator ""X (ch),其中 ch 是去掉 ud-suffix 的字面量。
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; // calls operator ""_w(1.2L)
u"one"_w; // calls operator ""_w(u"one", 3)
12_w; // calls operator ""_w("12")
"two"_w; // error: no applicable literal operator
}

当在 翻译阶段 6 中发生字符串字面量连接时,用户自定义字符串字面量也会被连接,并且它们的 ud-suffix 在连接时会被忽略,但前提是连接后的字面量上只能有一个后缀。

int main() {
L"A" "B" "C"_x; // OK: same as L"ABC"_x
"P"_x "Q" "R"_y; // error: two different ud-suffixes (_x and _y)
}

字面量运算符

由用户自定义字面量调用的函数称为字面量运算符(如果它是模板,则称为字面量运算符模板)。它就像任何其他 函数函数模板 一样在命名空间作用域中声明(它也可以是友元函数、函数模板的显式实例化或特化,或由 using-declaration 引入),除了以下限制:

此函数的名称可以有两种形式之一:

1operator ""
标识符 (已弃用)
2operator
user-defined-string-literal
pub标识符用于调用此函数的、用户自定义字面量的 ud-suffix 标识符
pubuser-defined-string-literal字符序列 " ",后面紧跟着成为 ud-suffix 的字符序列,中间没有空格
  1. 声明一个字面量运算符。
  2. 声明一个字面量运算符。此语法使得可以使用语言 关键字 和保留标识符作为 ud-suffix,例如,来自头文件 <complex> 的 operator ""if

 
ud-suffix 必须以_下划线开头:不以下划线开头的后缀保留给标准库提供的字面量运算符。也不能包含双下划线__:此类后缀也保留。

如果字面量运算符是模板,它必须具有空的参数列表,并且只能有一个模板参数,该参数必须是元素类型为 char 的非类型模板参数包(在这种情况下,它被称为数值字面量运算符模板

template<char...>
double operator ""_x();

或者是一个类类型的非类型模板参数(在这种情况下,它被称为字符串字面量运算符模板

struct A { constexpr A(const char*); };

template<A a>
A operator ""_a();
 (自 C++23 起)

字面量运算符只允许以下参数列表:

1
( const char* )
2
( unsigned long long int )
3
( long double )
4
( char )
5
( wchar_t )
6
( char8_t ) (自 C++20 起)
7
( char16_t )
8
( char32_t )
9
( const char*, std::size_t )
10
( const wchar_t*, std::size_t )
11
( const char8_t*, std::size_t ) (自 C++20 起)
12
( const char16_t*, std::size_t )
13
( const char32_t*, std::size_t )
    1. 具有此参数列表的字面量运算符是原始字面量运算符,用作整数和浮点数用户自定义字面量的后备(见上文)
    1. 具有这些参数列表的字面量运算符是用户自定义整数字面量的首选字面量运算符
    1. 具有这些参数列表的字面量运算符是用户自定义浮点数字面量的首选字面量运算符
  • 4-8) 具有这些参数列表的字面量运算符由用户自定义字符字面量调用
  • 9-13) 具有这些参数列表的字面量运算符由用户自定义字符串字面量调用

不允许使用默认参数
不允许使用C 语言连接

除了上述限制外,字面量运算符和字面量运算符模板都是正常的函数(和函数模板),它们可以声明为内联或 constexpr,可以具有内部或外部连接,可以显式调用,可以获取它们的地址等等。

#include <string>

void operator ""_km(long double); // OK, will be called for 1.0_km
void operator "" _km(long double); // same as above, deprecated
std::string operator ""_i18n(const char*, std::size_t); // OK

template<char...>
double operator ""(); // OK
float operator ""_e(const char*); // OK

// error: suffix must begin with underscore
float operator ""Z(const char*);

// error: all names that begin with underscore followed by uppercase
// letter are reserved (NOTE: a space between "" and _).
double operator"" _Z(long double);

// OK. NOTE: no space between "" and _.
double operator""_Z(long double);

// OK: literal operators can be overloaded
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; // error
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; // error
auto u = 1_p +2; // OK

整数或浮点数用户自定义字面量后面的点运算符也适用此规则。

#include <chrono>

using namespace std::literals;

auto a = 4s.count(); // Error
auto b = 4s .count(); // OK
auto c = (4s).count(); // OK

否则,会形成一个无效的预处理数字标记(例如,1.0_E+2.04s.count),这会导致编译失败。

特性测试宏标准注释
__cpp_user_defined_literals200809L(C++11)用户定义字面量

示例

#include <algorithm>
#include <cstddef>
#include <iostream>
#include <numbers>
#include <string>

// used as conversion from degrees (input param) to radians (returned output)
constexpr long double operator""_deg_to_rad(long double deg)
{
long double radians = deg * std::numbers::pi_v<long double> / 180;
return radians;
}

// used with custom type
struct mytype
{
unsigned long long m;
};

constexpr mytype operator""_mytype(unsigned long long n)
{
return mytype{n};
}

// used for side-effects
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 string literal operator template

template<std::size_t N>
struct DoubleString
{
char p[N + N - 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 中
puboperator""if
operator""i
operator""il
 (C++14 起)
一个 std::complex 字面量,表示纯虚数
定义在内联命名空间 std::literals::chrono_literals 中
puboperator""h (C++14 起)一个 std::chrono::duration 字面量,表示小时
puboperator""min (C++14 起)一个 std::chrono::duration 字面量,表示分钟
puboperator""s (C++14 起)一个 std::chrono::duration 字面量,表示秒
puboperator""ms (C++14 起)一个 std::chrono::duration 字面量,表示毫秒
puboperator""us (C++14 起)一个 std::chrono::duration 字面量,表示微秒
puboperator""ns (C++14 起)一个 std::chrono::duration 字面量,表示纳秒
puboperator""y (C++20 起)一个 std::chrono::year 字面量,表示特定年份
puboperator""d (C++20 起)一个 std::chrono::day 字面量,表示月份中的某一天
定义在内联命名空间 std::literals::string_literals 中
puboperator""s (C++14 起)将字符数组字面量转换为 basic_string
定义在内联命名空间 std::literals::string_view_literals 中
puboperator""sv (C++17 起)创建字符数组字面量的 string_view

缺陷报告

以下改变行为的缺陷报告已追溯应用于先前发布的 C++ 标准。

DR应用于发布时的行为正确行为
CWG 1473(C++11)在声明字面量运算符时," "ud-suffix 之间的空格是必需的已改为可选
CWG 1479(C++11)字面量运算符可以有默认参数已禁止
CWG 2521(C++11)operator" " _Bq 格式错误(不要求诊断),因为它使用了保留标识符 _Bq已弃用带空格的字面量运算符声明语法(" "ud-suffix 之间)