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

来自cppreference.com
< cpp‎ | language


 
 
C++ 语言
 
 

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

语法

用户定义字面量是下列形式的表达式之一:

十进制字面量 用户定义后缀 (1)
八进制字面量 用户定义后缀 (2)
十六进制字面量 用户定义后缀 (3)
二进制字面量 用户定义后缀 (4)
分数常量 指数部分 (可选) 用户定义后缀 (5)
数字序列 指数部分 用户定义后缀 (6)
字符字面量 用户定义后缀 (7)
字符串字面量 用户定义后缀 (8)
1-4) 用户定义整数字面量,例如 12_km
5-6) 用户定义浮点字面量,例如 0.5_Pa
7) 用户定义字符字面量,例如 'c'_X
8) 用户定义字符串字面量,例如 "abd"_Lu"xyz"_M
十进制字面量 - 整数字面量中相同,非零的十进制数位后随零或多个十进制数位
八进制字面量 - 整数字面量中相同,零后随零或多个八进制数位
十六进制字面量 - 整数字面量中相同,0x0X 后随一个或多个十六进制数位
二进制字面量 - 整数字面量中相同,0b0B 后随一或多个二进制数位
数字序列 - 浮点字面量中相同,一个十进制数字序列
分数常量 - 浮点字面量中相同,要么是一个后随小数点的 数字序列123.),要么是一个可选的 数字序列 后随小数点和另一个 数字序列1.0.12
指数部分 - 浮点字面量中相同,字母 e 或字母 E 后随可选的正负号,后随 数字序列
字符字面量 - 字符字面量中相同
字符串字面量 - 字符串字面量中相同,包括原始字符串字面量
用户定义后缀 - 标识符,由字面量运算符字面量运算符模板声明引入(见下文

整数浮点数字序列中,可以在任何两个数位间插入分隔符 ',并忽略它们。

(C++14 起)

如果一个记号同时匹配用户定义字面量的语法和普通字面量的语法,那么它被假定为普通字面量(即不可能重载 123LL 中的 LL)。

当编译器遇到一个带有 用户定义后缀 X 的用户定义字面量时,它进行无限定名字查找,寻找名为 operator ""X 的函数。如果找不到任何声明,那么程序非良构。否则,

1) 对于用户定义整数字面量:
a) 如果重载集包含带有形参类型 unsigned long long 的字面量运算符,那么用户定义字面量表达式会被当做函数调用 operator ""X(n ULL),其中 n 是无 用户定义后缀 的该字面量;
b) 否则,重载集要么包含原始字面量运算符,要么数值字面量运算符模板。如果重载集包含原始字面量运算符,那么用户定义字面量表达式会被当做函数调用 operator""X("n ")
c) 否则,如果重载集包含数值字面量运算符模板,则用户定义字面量表达式被当做函数调用 operator""X<'c1 ', 'c2 ', 'c3 '..., 'ck '>(),其中 c1..ckn 中的各个字符且它们都来自基本字符集
2) 对于用户定义浮点字面量:
a) 如果重载集包含带有形参类型 long double 的字面量运算符,那么用户定义字面量表达式会被当做函数调用 operator ""X(f  L),其中 f 是无 用户定义后缀 的该字面量;
b) 否则,重载集包含原始字面量运算符和数值字面量运算符模板其中的一种。如果重载集包含原始字面量运算符,那么用户定义字面量表达式会被当做函数调用 operator ""X("f  ")
c) 否则,如果重载集包含数值字面量运算符模板,那么用户定义字面量表达式会被当做函数调用 operator""X<'c1 ', 'c2 ', 'c3 '..., 'ck '>(),其中 c1..ckf 中的各个字符且它们都来自基本字符集
3) 对于用户定义字符串字面量,令 str 为无 用户定义后缀 的字面量:
a) 如果重载集包含带非类型模板形参的字符串字面量运算符模板,且 str 对它是良构的模板实参,则用户定义字面量表达式被当作函数调用 operator ""X<str>()
(C++20 起)
b) 否则,用户定义字面量表达式被当做函数调用 operator ""X(str, len),其中 len 是字符串字面量的长度,不包含终止空字符。
4) 对于用户定义字符字面量,用户定义字面量表达式被当做函数调用 operator ""X(ch),其中 ch 是无 用户定义后缀 的该字面量。
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)
标识符 - 作为用户定义字面量所用且会调用此函数的 用户定义后缀标识符
用户定义字符串字面量 - 字符序列 "" 后不带空格,后随将作为 用户定义后缀 的字符序列
1) 声明字面量运算符。
2) 声明字面量运算符。这种语法允许将语言关键词和保留标识符用作 用户定义后缀,例如来自标头 <complex>operator ""if

用户定义后缀 必须以下划线 _ 开始:不以下划线开始的后缀为标准库提供的字面量运算符所保留。 用户定义后缀 也不能包含双下划线 __:此类后缀也被保留。

如果字面量运算符是模板,那么它必须有空形参列表,并且只能有一个模板形参,模板形参必须是元素类型是 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)
1) 拥有此形参列表的字面量运算符是原始字面量运算符,用于整数和浮点用户定义字面量的后备方式(见上文)
2) 拥有这些形参列表的字面量运算符是用户定义整数字面量的首选字面量运算符
3) 拥有这些形参列表的字面量运算符是用于定义浮点字面量的首选字面量运算符
4-8) 拥有这些形参列表的字面量运算符由用户定义的字符字面量所调用
9-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);

由于最大吞噬规则,以 pP (C++17 起)eE 结束的用户定义整数和浮点字面量,在后随运算符 +- 时,必须在源码中以空白符或括号与运算符分隔:

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.04s.count),这会导致编译失败。

功能特性测试宏 标准 功能特性
__cpp_user_defined_literals 200809L (C++11) 用户定义字面量

关键词

operator

示例

#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 定义
表示小时的 std::chrono::duration 字面量
(函数)
表示分钟的 std::chrono::duration 字面量
(函数)
表示秒的 std::chrono::duration 字面量
(函数)
表示毫秒的 std::chrono::duration 字面量
(函数)
表示微秒的 std::chrono::duration 字面量
(函数)
表示纳秒的 std::chrono::duration 字面量
(函数)
表示特定年的 std::chrono::year 字面量
(函数)
表示月内日期的 std::chrono::day 字面量
(函数)
在内联命名空间 std::literals::string_literals 定义
转换字符数组字面量为 basic_string
(函数)
在内联命名空间 std::literals::string_view_literals 定义
创建一个字符数组字面量的字符串视图
(函数)

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 1473 C++11 字面量运算符声明中曾要求 ""用户定义后缀 间的空格 不再强制要求
CWG 1479 C++11 字面量运算符可以有默认实参 不能有默认实参
CWG 2521 C++11 operator"" _Bq 使用了保留的标识
_Bq,因此它非良构(不要求诊断)
弃用 ""用户定义后缀 之间
有空格的字面量运算符语法