C/C++ macro challenge #1: BOOST_PP_COUNTER

I can think of two potential approaches to this. Both use the uniqueness of anonymous opaque {} types, which has also been (ab)used for other interesting purposes in the past.

The first one is slightly less hacky, but has the major disadvantages that 1. the returned value is not comptime-known and 2. it wastes one byte of space for each counter value in the final binary:

const std = @import("std");

pub fn counter(comptime T: type) usize {
    if (@typeInfo(T) != .Opaque or @typeInfo(T).Opaque.decls.len != 0) {
        @compileError("pass opaque {} as the argument");
    }

    return @intFromPtr(&struct {
        var t: u8 = 0;
    }.t);
}

pub fn main() !void {
    std.debug.print("{}\n", .{counter(opaque {})});
    std.debug.print("{}\n", .{counter(opaque {})});
}

(as described in [Feature Request] @typeInfo of a `type` with a unique identifier · Issue #5459 · ziglang/zig · GitHub; the linked issue being solved would make this more natural)

The second approach is more reliant on implementation details, but it does work at comptime and should not waste any space:

const std = @import("std");

pub fn counter(comptime T: type) comptime_int {
    if (@typeInfo(T) != .Opaque or @typeInfo(T).Opaque.decls.len != 0) {
        @compileError("pass opaque {} as the argument");
    }
    const type_name = @typeName(T);
    const qualifier_index = std.mem.lastIndexOfScalar(u8, type_name, '_') orelse
        @compileError("pass opaque {} as the argument");
    var value: comptime_int = 0;
    for (type_name[qualifier_index + 1 ..]) |c| {
        value = 10 * value + (@as(comptime_int, c) - '0');
    }
    return value;
}

const a = counter(opaque {});
const b = counter(opaque {});

pub fn main() !void {
    std.debug.print("{}\n", .{a});
    std.debug.print("{}\n", .{b});
    std.debug.print("{}\n", .{counter(opaque {})});
    std.debug.print("{}\n", .{counter(opaque {})});
}
1 Like

Interesting, I did not realize that type (T != U) when T and U are sequentially declared opaques. I suppose it makes sense, they are apparently unique. If T and U are u32 for example, everything returns true and finishes up. Well this is a fascinating approach and some grade A hacking - so far, this has my vote. Like you said, this is very dependent on future type taxonomy, but still very interesting none the less.

–

Very interesting approach. I think we can make it less reliant on implementation details by converting the typename to a big comptime_int, then pass that to a regular comptime id allocator. As long as the names are guaranteed to be unique, we would get unique comptime_ints. We wouldn't need to care about what's in the string at all. We would also be able to start the counter at any value we want and increment (or decrement) it at any interval.

–

It seems that struct{} has the same type uniqueness as opaque{} and it does not waste a byte.

–

@slonik-az just to clarify, the wasted byte in the first approach is not due to the opaque{} (that's just to ensure that the counter function is instantiated again and doesn't use any space), but because of the var t whose address is taken to get the unique value. Basically the first approach boils down to "non-zero-sized global variables must have unique addresses", and uses a single byte per global to achieve that.

–