Using Zig for C++ metaprogramming

Hi, first time posting on Ziggit!

I had a shower thought today and I’d like to know how plausible it is.

I am one of those people who is unfortunately stuck with C++ in their day job. One area that is very frustrating for me in C++ is the metaprogramming (do not get me started on the syntax for the newly accepted reflection in C++26). So, my thought was this: what if I can use Zig for my metaprogramming needs by generating C headers and then gluing it all together with some minimal C++ code?

I would ideally like to leverage the caching functionality of the Zig buildsystem and then just minimally modify the main CMake file to call something like zig build gen.

I noticed that there is functionality for generating header files, but it is not clear how well of a supported use case it is currently, seeing that there is this open issue #9698

I would appreciate input from anyone who has tried or has experience with this type of thing, thanks!

3 Likes

Hi @minkatter, welcome to Ziggit.

That is an intriguing proposition. While I haven’t personally done what you are suggesting, I wanted to add some context.

Currently, the emit-h capabilities of the compiler have not been added back to the self-hosted compiler, although there have been attempts. There was a recent PR attempt, but it got stale and didn’t make it in. Most people write their own header files. Tigerbeetle actually has a program to do it: tigerbeetle/src/clients/c/tb_client_header.zig at main · tigerbeetle/tigerbeetle · GitHub

Are you wanting to take C++ files and run those through zig to do the metaprogramming? Or are you wanting to write structs/comptime in Zig and translate that into C/C++ headers?

I am looking to do more like the latter. I would like to use comptime to generate some structs and helper functions that I can then translate to C structs. My understanding is that I might be limited by the fact that Zig actively tries to prevent injecting new types, so I might need workarounds like having my own string processing, but I would still be more comfortable doing that in Zig.

Having now taken a look at @Southporter 's link, it would seem string processing is what TigerBeetle does:

fn emit_struct(
    buffer: *std.ArrayList(u8),
    comptime type_info: anytype,
    comptime c_name: []const u8,
) !void {
    try buffer.writer().print("typedef struct {s} {{\n", .{c_name});

    inline for (type_info.fields) |field| {
        try buffer.writer().print("    {s} {s}", .{
            resolve_c_type(field.type),
            field.name,
        });

        switch (@typeInfo(field.type)) {
            .array => |array| try buffer.writer().print("[{d}]", .{array.len}),
            else => {},
        }

        try buffer.writer().print(";\n", .{});
    }

    try buffer.writer().print("}} {s};\n\n", .{c_name});
}

Thanks for the reference!

3 Likes