如何创建可表示自定义数据类型的用户定义字面量
自定义字面量的本质是给语言的字面量解析流程绑定自定义转换逻辑,让编译器/解释器碰到符合规则的字面量写法时,自动调用转换逻辑返回目标自定义类型的实例,不用每次都手动调用构造函数。不同编程语言的实现机制存在差异,下面是几种主流语言的具体实现方式:
C++(C++11及以上版本原生支持)
C++通过字面量运算符实现自定义字面量,要求定义的运算符函数名必须以operator""开头,后面跟自定义的后缀名,函数参数对应要处理的原始字面量类型。
举个例子,如果你定义了表示长度单位“米”的Meter类型,想直接写1.5_m就得到对应实例:
class Meter { private: double val; public: constexpr Meter(double v):val(v) {} double value() const {return val;} }; // 定义后缀为_m的字面量运算符,注意自定义后缀必须以下划线开头 constexpr Meter operator"" _m(long double raw_val) { return Meter(static_cast<double>(raw_val)); } int main() { // 直接用字面量创建对象,不用显式写Meter(1.5) auto len = 1.5_m; return 0; }
除了浮点数字面量,你也可以给整数、字符串、字符等所有内置字面量类型定义自定义后缀,比如定义"xxx"_mystr直接生成自定义字符串类实例。注意不带下划线的后缀是C++标准库预留的,自定义时不要使用,避免和未来的标准实现冲突。
Rust
Rust的数值字面量本身是多态的,只要自定义类型实现了对应类型的转换trait,就可以直接用字面量赋值;如果要实现类似C++的后缀式自定义字面量,可以通过过程宏或者const fn实现,比如很多时间处理库就实现了30s、10ms这种写法,直接生成Duration类型实例。
举个数值字面量适配自定义类型的例子:
#[derive(Debug)] struct Second(u64); // 实现从u64无符号整数到Second类型的转换 impl From<u64> for Second { fn from(raw: u64) -> Self { Second(raw) } } fn main() { // 直接用整数字面量转换得到Second实例 let wait: Second = 30.into(); println!("{:?}", wait); // 输出Second(30) }
Scala
Scala通过StringContext + 隐式类实现自定义插值型字面量,写法和原生的s"xxx"、f"xxx"插值字符串完全一致,自定义的字面量可以直接解析插值内容返回目标类型。
比如自定义摄氏度类型的字面量:
case class Celsius(degree: Double) // 绑定自定义字面量deg implicit class CelsiusInterpolator(sc: StringContext) { def deg(args: Any*): Celsius = { val raw = sc.s(args: _*).toDouble Celsius(raw) } } object Main extends App { // 直接用deg"36.5"得到Celsius实例 val bodyTemp = deg"36.5" println(bodyTemp) // 输出Celsius(36.5) }
Python
Python没有原生的自定义字面量语法,一般通过运算符重载、AST改写或者第三方语法扩展库模拟类似效果,最常见的是用右乘运算符@模拟后缀效果:
class Kilometer: def __init__(self, val: float): self.val = val def __repr__(self): return f"{self.val} km" class LiteralHelper: def __rmatmul__(self, raw_val): return Kilometer(float(raw_val)) km = LiteralHelper() # 模拟字面量效果,10@km直接得到Kilometer实例 distance = 10@km print(distance) # 输出10 km
- 自定义字面量是为了简化重复代码、提升可读性,别为了炫技滥用,规则要符合团队常识,不要写其他人看不懂的奇怪后缀
- 字面量的转换逻辑尽量保持纯函数,不要加IO、随机数这类副作用逻辑,不然代码行为会很难排查
- 支持编译期计算的语言(C++、Rust等)尽量把字面量解析、校验逻辑放到编译期执行,错误提前暴露,不要留到运行时崩溃
内容的提问来源于stack exchange,提问作者Supergamer

