比较 C++、Zig 和 C3 的反射能力
摘要
本文比较了 C++、Zig 和 C3 编程语言的编译时反射能力。它通过代码示例展示了每种语言如何处理枚举到字符串转换和结构体内省等任务。
暂无内容
查看缓存全文
缓存时间: 2026/09/20 03:30
# 比较C++、Zig和C3的反射能力
来源:https://nyr24.github.io/blog/reflection-comparison/
反射允许程序在运行时或编译时检查并操作自身的结构。所有C++(即将支持反射)、Zig和C3都依赖于编译时反射,因此你可以以零运行时开销来推导类型、枚举器和结构体成员。本文将比较这些语言处理编译时反射的方式。
### 什么是C3?
https://nyr24.github.io/blog/reflection-comparison/#what-is-c3
C3是一门相对较新的编程语言,主要关注可读性、性能、极简性,并为C/C++程序员提供熟悉感。它没有繁重的运行时、垃圾回收、异常或RAII。它还开箱即用地完全支持C ABI兼容性。C3使用特殊语法进行编译时执行:所有变量、控制流结构都带有**$**前缀。这样做是为了明确向读者展示哪些代码在编译时运行。它使用**宏**进行编译时求值和反射。
> C3宏旨在提供C预处理器宏的替代方案。它们扩展了此类宏,提供使用常量折叠进行编译时求值的功能,从而提供一种对IDE友好、有限制的、编译时执行的能力。
让我们看看这些语言的实际表现!
### 枚举转字符串
https://nyr24.github.io/blog/reflection-comparison/#enum-to-string-conversion
C++:
```
enum class Color { Red, Green, Blue };
template constexpr std::string_view enum_to_string(E value) {
template inline for (constexpr auto r : std::meta::enumerators_of(^^E)) {
if (value == [:r:]) {
return std::meta::identifier_of(r);
}
}
return "Unknown";
}
int main(){
Color color = Color::Red;
printf("%s", enum_to_string(color));
return 0;
}
```
Zig:
```
const Color = enum {
RED,
GREEN,
BLUE,
pub fn to_string(color: Color) []const u8 {
switch (color) {
.RED => return "red",
.GREEN => return "green",
.BLUE => return "blue",
}
}
};
pub fn main() !void {
const c: Color = .BLUE;
std.debug.print("{s}", .{c.to_string()});
// 输出:
// blue
}
```
在Zig中,我能想到的唯一解决方案是为每个想转成字符串的枚举附加一个方法,这不是通用的方法。我并非资深Zig专家,你可以在评论中纠正我。
C3:
```
enum Color { RED, GREEN, BLUE }
macro String enum_to_string($enum_val){
var $EnumType = $Typeof($enum_val);
$foreach $val : $EnumType::values:
$if $val == $enum_val:
return $val.description;
$endif
$endforeach
}
fn void main(){
Color $color = RED;
String $color_name = enum_to_string($color);
io::printfn("%s", $color_name);
}
```
在C3中,枚举具有特殊属性。例如,如果你想打印枚举值,它会以可读的形式打印,就像在源代码中定义的那样。例如,代码:`io::printfn(“%s”, Color.RED)`会输出RED,而不是0。如果你想获取枚举的底层值,你可以访问`.ordinal`或将其转换为底层类型。你还可以将任意类型的值与枚举器关联:
```
enum Color : uint (String str_repr, char amount_of_red){
RED { "Red Color", 255 }
BLUE { "Blue Color", 0 }
}
fn void log_color(Color c){
io::printfn("%s %s", c.str_repr, c.amount_of_red);
// 输出:Red Color 255
}
```
让我们继续讨论反射!
### 结构体内省
https://nyr24.github.io/blog/reflection-comparison/#struct-introspection
C++:
```
struct Person {
std::string_view name;
int age;
double height;
};
template void print_struct_fields(const T& obj) {
std::cout << std::meta::identifier_of(^^T) << " details:\n";
template inline for (constexpr auto member : std::meta::nonstatic_data_members_of(^^T)) {
constexpr std::string_view member_name = std::meta::identifier_of(member);
std::cout << " " << member_name << ": " << obj.[:member:] << "\n";
}
}
int main() {
Person alice{"Alice Smith", 30, 1.75};
print_struct_fields(alice);
/* 输出:
Person details:
name: Alice Smith
age: 30
height: 1.75 */
}
```
Zig:
```
const Person = struct {
name: []const u8,
age: i32,
height: f64,
};
fn printStructFields(value: anytype) void {
comptime {
std.debug.assert(@typeInfo(@TypeOf(value)) == .@"struct");
}
inline for (@typeInfo(@TypeOf(value)).@"struct".fields) |field| {
switch (field.type) {
[]const u8 => {
std.debug.print("{s}: {s},\n", .{ field.name, @field(value, field.name) });
},
else => {
std.debug.print("{s}: {any},\n", .{ field.name, @field(value, field.name) });
},
}
}
}
pub fn main() !void {
const alice = Person{
.name = "Alice Smith",
.age = 30,
.height = 1.75,
};
std.debug.print("Person Details:\n", .{});
printStructFields(alice);
// 输出:
// Person details:
// name: Alice Smith
// age: 30
// height: 1.750000
}
```
C3:
```
struct Person{
String name;
int age;
double height;
}
<*
@require @kindof($val) == STRUCT : "Expected a struct" // (1)
*>
macro void print_struct_fields($val){
var $Type = $Typeof($val);
$foreach $field : $Type::members:
io::printfn("\t%s: %s", $field.name, $val.$field);
$endforeach
}
fn void main(){
Person $alice = {"Alice Smith", 30, 1.75};
io::printfn("Person details: ");
print_struct_fields($alice);
/* 输出:
Person details:
name: Alice Smith
age: 30
height: 1.750000 */
}
```
这里,(1) C3使用了称为“契约”的可选前置条件,可以极大地帮助进行输入验证。它们将在编译时执行(如果可能),否则将在运行时执行。
### 使用仅编译时属性进行验证
https://nyr24.github.io/blog/reflection-comparison/#validation-with-compile-time-only-attributes
C++:
```
struct Range {
int lo;
int hi;
}
struct Config{
[[=Range{ 1, 65535 }]] int port;
[[=Range{ 1, 256 }]] int max_threads;
[[=Range{ 100, 30000 }]] int timeout_ms;
}
template constexpr bool validate(const T& obj){
constexpr auto context = std::meta::access_context::current();
template for (constexpr auto member: define_static_array(
nonstatic_data_members_of(^^T, context)) {
template for (constexpr auto annotation : define_static_array(
annotations_of_with_type(member, ^^Range))) {
auto [lo, hi] = extract(annotation);
if (obj.[:member:] < lo) return false;
else if (obj.[:member:] > hi) return false;
}
}
return true;
}
static_assert(validate(Config{ 1000, 50, 20000 }));
static_assert(validate(Config{ 0, 0, 0 })); // 编译失败。
```
Zig:很遗憾,Zig没有“属性”或任何将编译时数据附加到结构体成员的替代方案。
C3:
```
struct Range {
int lo;
int hi;
}
attrdef @Range(r) = @tag("range", r);
struct Config{
int port @Range({1, 65535});
int max_threads @Range({1, 256});
int timeout_ms @Range({100, 30000});
}
enum ValidationResult { TO_LOW, TO_HIGH, SUCCESS }
// (1)
macro ValidationResult validate_comptime($obj) @const{
var $Type = $Typeof($obj);
$foreach $field : $Type::members:
$if $field.has_tag("range"):
Range $r = $field.get_tag("range");
$if $obj.$field < $r.lo:
return TO_LOW;
$endif
$if $obj.$field > $r.hi:
return TO_HIGH;
$endif
$endif
$endforeach
return SUCCESS;
}
// (2)
macro ValidationResult validate_runtime(obj){
var $Type = $Typeof(obj);
Range r @noinit;
$foreach $field : $Type::members:
$if $field.has_tag("range"):
r = $field.get_tag("range");
if (obj.$field < r.lo) return TO_LOW;
if (obj.$field > r.hi) return TO_HIGH;
$endif
$endforeach
return SUCCESS;
}
fn void main(){
Config $c1 = { .port = 1000, .max_threads = 50, .timeout_ms = 20000 };
Config $c2 = { .port = 0, .max_threads = 0, .timeout_ms = 0 };
Config c1 = { .port = 1000, .max_threads = 50, .timeout_ms = 20000 };
Config c2 = { .port = 0, .max_threads = 0, .timeout_ms = 0 };
io::printn(validate_comptime($c1));
io::printn(validate_comptime($c2));
io::printn(validate_runtime(c1));
io::printn(validate_runtime(c2));
/* 输出:
SUCCESS
TO_LOW
SUCCESS
TO_LOW */
}
```
在这个C3示例中,我想向你展示两种选项。在第一种(1)变体中,我们在编译时验证一切,我们可以通过在宏上添加`@const`属性轻松验证这一点。在第二种(2)变体中,我们将编译时属性与运行时验证相结合。在这个示例中,你可以看到`$if`和`if`之间的语法区别如何帮助理解哪些代码在编译时展开,哪些将在运行时执行。
### 结论
https://nyr24.github.io/blog/reflection-comparison/#conclusions
所有观察的语言都能进行真正的编译时反射,这对序列化器、调试打印器和像上面那样的通用助手非常有益。代价是易用性:C++通过冗长的模板机制和插值获得能力,而C3通过其宏系统和编译时执行的特殊语法使相同的思想更具可读性和表现力,很容易理解代码将在何时编译时执行,何时不会。Zig则没有宏,它依赖于`comptime`函数和块、内联for循环和类型内省内置函数,这也是一种良好的、现代的、大多数情况下可读的方法。
就个人而言,我发现C3是一个非常有前景的系统编程语言,需要更多关注;每个人都知道C++,而Zig的市场推广做得很好,但C3缺乏那种市场推广,尽管它可以轻松地与Zig、Odin或任何其他新的系统编程语言竞争。而且它在每个次版本中没有大量的破坏性更改。它比Zig稳定得多(说实话,Zig在开发超过10年后仍停留在0.1x版本是相当尴尬的),而C3已经到了0.8.x版本,1.0非常接近了,参见路线图(https://c3-lang.org/getting-started/roadmap/)。你可以在主网站上搜索更多关于C3的信息(https://c3-lang.org/)。想讨论这门语言或有疑问?加入官方Discord服务器(https://discord.gg/qN76R87)。
相似文章
为 Zig 打包的 C/C++ 项目
一个工具,可将现有的 C/C++ 项目打包以供 Zig 构建系统使用,从而实现更简单的集成。
重返Zig
作者描述了从Zig到Rust再回到Zig的历程,探讨了编程语言中稳定性与表达力之间的权衡。
枚举转字符串的开销:C++26 反射与旧方法对比
本文使用 GCC 16 基准测试了 C++26 反射在枚举转字符串转换中的编译时开销,并将其与 C++17 库和 X 宏预处理器技术进行了对比。
用 Zig 写一个 C 编译器
一位开发者记录了用 Zig 语言、按照 Nora Sandler 的教程系列构建名为 paella 的 C 编译器的全过程。
Zig 示例教程
通过带注释的示例,对 Zig 编程语言进行实践性介绍,涵盖从基础到高级的主题。灵感来源于 Go by Example。