Appearance
运算符
本文档记录 cp 运算符表和第一版运算符重载规则。完整类型检查规则见 type_system.md。
整体优先级接近 C++,但逻辑运算使用关键字形式。
逻辑运算
cp 使用:
cp
not value
left and right
left or right不使用 C/C++ 的:
text
! && ||规则:
not操作数必须是bool,结果为bool。and/or两侧必须是bool,结果为bool。
算术运算
text
+ - * / %规则:
- 算术运算要求数值类型。
- 整数族内部可以统一类型。
- 浮点族内部可以统一类型。
%只允许整数类型。+/-也支持指针算术,见“指针运算”。
一元 + / - 只要求操作数是数值类型,结果类型与操作数读出的类型一致。
指针运算
cp 的裸指针运算保持接近 C++:
cp
let next = p + 1;
let prev = next - 1;
let distance: isize = next - p;
let value = p[0];规则:
p + n、n + p、p - n要求p是T*,n是整数类型,结果为T*。- 指针加减以
T为元素步长。 p2 - p1要求两侧是相同目标类型的指针,结果为isize。- 指针差值表示元素数量,不是字节数量。
p[i]要求p是指针类型,i是整数类型,语义等价于*(p + i)。- 不支持
i[p]。即使i是整数、p是指针,下标目标也必须写在[]左侧。 - 有定义的边界与 C++ 保持一致:同一数组对象或同一连续分配对象内的位置,以及 one-past 位置。
- 编译器只做类型检查,不静态证明边界、生命周期或指针来源。
比较运算
text
== != < <= <=> > >=规则:
== != < <= > >=结果为bool。<=>是普通可重载二元运算;标准库约定返回partial_ordering、weak_ordering或strong_ordering这类比较分类值。- 数值类型按共同数值类型比较。
- 相同目标类型的指针支持
==/!=。 - 相同目标类型的指针支持
< <= > >=,有定义边界与 C++ 指针有序比较一致。 - 非数值类型不提供通用内建比较。特定类型若需要比较能力,应通过
operator重载或标准库协议提供。
显式声明的 operator ==、operator !=、operator <、operator <=、operator >、operator >= 优先于任何基于 <=> 的派生策略。第一版标准库对 str 直接提供这些比较 operator,并以 operator <=> 作为三路比较入口;后续若在编译器层接入派生比较,应保持同样的显式 operator 优先规则。
位运算
text
~ & | ^ << >>规则:
- 位运算要求整数类型。
- 结果类型为统一后的整数类型。
运算符重载
cp 支持一版受限的 operator 重载。它补充内建运算符,不打开普通函数和成员函数的完整重载体系。
语法总览
text
TopLevelOperator -> export? operator OverloadableOperator ( ParameterList? ) ReturnType? FunctionBody
ImplOperator -> operator OverloadableOperator ( ParameterList? ) ReturnType? FunctionBody
OverloadableOperator
-> UnaryOperator
| UpdateOperator
| BinaryOperator
| ComparisonOperator
| AssignmentOperator
| SubscriptOperator
| CallOperator
UnaryOperator -> "+" | "-" | "~"
UpdateOperator -> "prefix" "++" | "postfix" "++" | "prefix" "--" | "postfix" "--"
BinaryOperator -> "+" | "-" | "*" | "/" | "%" | "&" | "|" | "^" | "<<" | ">>"
ComparisonOperator
-> "==" | "!=" | "<" | "<=" | "<=>" | ">" | ">="
AssignmentOperator
-> "=" | "+=" | "-=" | "*=" | "/=" | "%="
| "&=" | "|=" | "^=" | "<<=" | ">>="
SubscriptOperator
-> "[]"
CallOperator -> "()"实际词法中 operator 和后面的运算符 token 可以有空白;文档统一写成 operator +、operator []、operator ()、operator prefix ++。
成员 operator 写在固有 impl 中,挂到当前类型命名空间:
cp
impl vec2 {
operator +(self const&, rhs: this const&) -> this
{
return vec2{ .x = x + rhs.x, .y = y + rhs.y };
}
operator ==(self const&, rhs: this const&) -> bool
{
return x == rhs.x and y == rhs.y;
}
operator prefix ++(self&) -> this&
{
x += 1;
y += 1;
return ref self;
}
operator ()(self const&, scale: i32) -> i32
{
return x * scale + y;
}
}顶层 operator 写作普通顶层声明,遵循模块导出和导入规则:
cp
operator +(left: vec2 const&, right: vec2 const&) -> vec2
{
return vec2{ .x = left.x + right.x, .y = left.y + right.y };
}声明规则:
operator是特殊声明,不占用普通函数名空间。- 顶层 operator 至少有一个参数类型必须是用户定义名义类型,或者该类型的引用。
- 固有
impl内的 operator 至少有一个参数必须是self、self&、self const&或this相关类型。 - 同一个类型命名空间内,相同 operator 可以按参数类型重载。
- 当前可见的顶层 operator 可以按参数类型重载。
- 普通函数、普通成员函数和关联函数仍然不支持重载。
- operator 不参与 UFCS,不能写成
operator +(a, b)普通调用。 operator ++和operator --不是合法声明;必须写成operator prefix ++、operator postfix ++、operator prefix --或operator postfix --,避免前后置声明歧义。
查找规则:
- 一元运算
op value先尝试内建规则;内建规则不成立时,查找value类型命名空间中的operator op;如果类型命名空间中没有该 operator,再查当前可见顶层operator op。 - 前置
++value/--value先尝试内建整数规则;内建规则不成立时分别查找operator prefix ++/operator prefix --。 - 后置
value++/value--先尝试内建整数规则;内建规则不成立时分别查找operator postfix ++/operator postfix --。 - 二元运算
left op right先尝试内建规则;内建规则不成立时,依次查找left类型命名空间、right类型命名空间、当前可见顶层operator op。 - 赋值
left = right和复合赋值left op= right先检查left必须是可写左值;然后查找left类型命名空间中的对应 operator;找不到时再查当前可见顶层 operator;仍然找不到时才使用允许的内建赋值或报错。 - 下标
value[index]先尝试内建[T; N]、str和指针规则;内建规则不成立时,查找value类型命名空间中的operator [];找不到时再查当前可见顶层operator []。元组字段使用.0/.1,不参与operator []。 - 调用
callee(args...)先按普通函数、函数值、lambda 和闭包调用检查;这些规则不成立时,查找callee类型命名空间中的operator (),调用参数序列为callee, args...。 - 某一级查找中如果存在同 operator 声明,但没有可行候选,直接报告参数不匹配或二义性,不继续回退到后续层级。
候选选择使用小型重载规则:
- 参数数量必须一致。
- 完全类型匹配优先于需要内建转换的匹配。
- 非模板候选优先于需要实例化后才确定的泛型候选。
- 如果最高优先级仍有多个候选,报二义性错误。
不支持 C++ 完整重载体系中的用户自定义隐式转换、模板偏序和 ADL。顶层 operator 只从当前模块和 import 引入的可见声明中查找。
operator () 不参与 UFCS,只能通过调用表达式触发;不能写成普通成员调用或顶层函数调用来绕过调用语法。
赋值与复合赋值
text
= += -= *= /= %= &= |= ^= <<= >>=规则:
- 赋值左侧必须是左值。
- 不能给
constbinding 重新赋值。 - 简单赋值
=可以由operator =重载。 - 没有可用
operator =时,简单赋值按左侧类型检查右侧表达式并使用内建赋值。 operator =的左操作数必须是可写引用;成员形式必须以self&作为第一个参数。- 复合赋值可以由对应
operator +=、operator -=等重载。 - 没有可用复合赋值 operator 时,不自动退化为
left = left op right;第一版要求用户显式声明复合赋值能力。 - 复合赋值 operator 的返回类型可以是内部
unit,也可以是左操作数引用类型,例如成员形式的this&。
示例:
cp
impl vec2 {
operator =(self&, rhs: this const&) -> this&
{
x = rhs.x;
y = rhs.y;
return self;
}
operator +=(self&, rhs: this const&)
{
x += rhs.x;
y += rhs.y;
}
}下标运算
cp 支持内建下标运算符和 operator []:
cp
value[index]规则:
- 下标运算属于后缀表达式,优先级高于一元前缀运算。
- 内建下标目标支持
[T; N]、str和指针。 [T; N]下标要求index是整数类型,结果类型为T。str下标要求index是整数类型,结果类型为char。T*下标要求index是整数类型,结果类型为T左值;T const*下标结果是只读T左值。- 指针下标只支持
p[i],不支持 C/C++ 的i[p]对称写法。 - 如果目标是可写左值且元素本身可写,数组和指针下标结果是可写左值。
str下标结果只读,不是可写左值。- 内建下标规则不成立时,可以查找
operator []。 operator []可以返回值,也可以返回引用;返回引用时,value[index]可以作为左值参与赋值。- 用于可变容器时,推荐同时提供
self&和self const&两个版本。 - 下标越界规则、常量下标诊断和内建类型的具体左值规则见 type_system.md。
- 空
[]在表达式中不是下标运算;数组字面量[]/[a, b]是单独的字面量语法。
示例:
cp
impl vector<T> {
operator [](self&, index: usize) -> T&
{
return data[index];
}
operator [](self const&, index: usize) -> T const&
{
return data[index];
}
}函数调用
函数调用写作:
cp
callee(arg1, arg2)规则:
callee可以是普通命名函数、成员函数、关联函数、函数类型值、函数指针值或闭包类型值。- 调用普通命名函数、成员函数和关联函数时,按对应声明检查参数和返回类型。
- 调用
f(...) -> R函数类型值时,参数必须能按函数类型的形参类型检查,结果类型为R。 - 调用
f*(...) -> R函数指针值时,参数检查和结果类型同函数类型;如果指针为空,属于底层 unsafe 契约。 - 有捕获闭包的调用规则见 lambda.md。
- 函数类型中的形参名只用于诊断,不参与调用匹配。
取址与解引用
cp 支持内建取址和解引用:
cp
let p = &value;
let value = *p;
*p = 1;规则:
&expr要求expr是左值,结果为指向该值的指针。- 如果
expr只能只读访问,&expr的结果为 target const 指针。 *ptr要求ptr是指针类型,结果为被指向对象的左值。T const*解引用得到只读左值。- 解引用的空指针、悬垂指针、未对齐指针、越界指针和未开始生命周期的对象属于底层 unsafe 契约。
自增与自减
cp 支持前置和后置自增、自减;内建整数路径和用户自定义 operator 使用同一表达式语义:
cp
++value
--value
value++
value--规则:
- 操作数必须是可写左值。
- 不能作用于
constbinding 或 const target。 - 内建路径只允许整数类型,不支持
bool、浮点、指针、array或tuple。 - 内建自增语义等价于把操作数加
1后写回,自减语义等价于把操作数减1后写回。 - 非内建类型按表达式位置查找对应 operator:前置
++value查找operator prefix ++,后置value++查找operator postfix ++;--同理。 - 前置形式先更新,再产生更新后的自身引用;内建前置结果为
T&,自定义 operator 推荐返回this&。 - 后置形式先产生旧值,再更新;内建后置结果为
T,自定义 operator 推荐返回this。 - 操作数表达式只求值一次。
自定义声明必须显式写出前置或后置位置,裸 operator ++ / operator -- 不是合法声明:
cp
impl counter {
operator prefix ++(self&) -> this&
{
value += 1;
return ref self;
}
operator postfix ++(self&) -> this
{
let old = self;
value += 1;
return old;
}
}转换运算
cp
value as typeas 进入显式转换检查。
优先级
具体优先级由 parser 的共享运算符表定义。设计目标是与 C++ 接近,并保留以下差异:
- 逻辑运算使用
and/or/not。 - 下标
value[index]属于后缀表达式,优先级高于一元前缀运算。 - 函数调用
callee(args...)属于后缀表达式,优先级高于一元前缀运算。 - 后置
value++/value--属于后缀表达式,优先级高于一元前缀运算。 - 前置
++value/--value属于一元前缀运算,和not、一元+、一元-、~同级。 - 一元
&value和*ptr属于一元前缀运算。 - 不支持
<=>。 - 不引入 C++ 式多种命名 cast 运算符。