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 <char...> 返回值类型 operator"" 自定义后缀 ();

注意,其中的自定义后缀 必须以下划线 _ 开头,并符合标识符命名规范。

一些事实

其实“以下划线开头”并非是从语法上强制性的。
如果你坚持不以下划线开头,那么编译器会给你一个警告:
[Warning] literal operator suffixes not preceded by '_' are reserved for future standardization [-Wliteral-suffix]
但是毕竟只是一个“警告”。


另外,由于字面量的特殊语法结构,自己定义的这个后缀其实可以同时是 C++ 关键字而不产生冲突,这是合法的。
如 C++ 标准库 <complex> 中就定义了 if 来把整形和浮点型转换为 complex<float>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wliteral-suffix"
constexpr std::complex<float>
operator""if(long double __num)
{ return std::complex<float>{0.0F, static_cast<float>(__num)}; }
constexpr std::complex<float>
operator""if(unsigned long long __num)
{ return std::complex<float>{0.0F, static_cast<float>(__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 语言链接。

除了上述限制之外,字面量运算符(和字面量运算符模板)是普通函数(和函数模板),它们可以声明为 inlineconstexpr ,它们可能具有内部或外部链接,它们可以显式调用,它们的地址可以被获取等。

注意

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

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),这导致编译失败。

最大吞噬规则

最大吞噬规则指的是编译器在处理源文件时,翻译阶段中解析预处理记号的一种特性。
如果一个给定字符前的输入已被解析为预处理记号,下一个预处理记号通常会由能构成预处理记号的最长字符序列构成,即使这样处理会导致后续分析失败。这常被称为最大吞噬。
比如经典的 i+++++i 会被解析为 i++ ++ +i 而报错,而不是人们所想的 i++ + ++i

示例

#include <iostream>
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;
}
输出结果
output: 114514
output: 3.14
print value: 2022
round: 10
        9
ascii: 65
strlen: 4

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

posted @   Gyan083  阅读(2553)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类
点击右上角即可分享
微信分享提示