----------------------------------
import std.traits;
import std.typecons;
alias MyInt = Typedef!int;
pragma(msg, hasMember!(int, "opCmp"));
pragma(msg, hasMember!(MyInt, "opCmp"));
----------------------------------
This prints false then true. It doesn't work very well in generic code.
Comment #1 by simen.kjaras — 2019-08-09T07:14:08Z
This would perhaps more correctly be filed as 'int does not have opCmp'. Since int does support the <, <=, >=, > operators it kinda, sorta has opCmp, but no member by that name. That's not really an option for Typedef, as it's not a magic type like int. At the same time, we absolutely expect MyInt.init < MyInt.init to compile.
There may be a good argument for adding opCmp and friends to built-in types to make generic code more generic, but removing them from Typedef is no solution.
Comment #2 by simen.kjaras — 2019-08-09T07:51:39Z
Alternatively, Typedef should contain only a single member and use aliases for the rest. Something like this:
struct Typedef(T, T init = T.init, string cookie=null) {
// Renaming the current Typedef:
TypedefImpl!(T, init, cookie) _payload;
alias _payload this;
static foreach (e; __traits(allMembers, T))
mixin("alias "~e~" = _payload."~e~";");
}
Comment #3 by atila.neves — 2019-08-09T08:21:20Z
But `int` *doesn't* have `opCmp`. I found this by trying to wrap a generic type for Python and implementing the comparison. std.typecons.Typedef completely broke things and I have to special-case for it, because `hasMember!(Typedef, "opCmp")` returns true, but then one can't actually forward to it because it doesn't actually exist.
Incidentally, the reason the bug happens is because `Typedef` uses `Proxy` in its implementation, and `Proxy` defines `opCmp`.
Comment #4 by simen.kjaras — 2019-08-09T11:37:22Z
But `int` *is* comparable using <, <=, >=, and >, so any (non-built-in) type pretending to be an int needs to have `opCmp`. What you're asking for is basically to cripple `Typedef` because you need to write some workaround code. Alternatively, you're asking for what I said in comment 2, but have elected to ignore that.
Actually, even with my `alias this` solution in comment 2, you get two members: `_payload`, and `opAssign`. The former we've defined, the latter the compiler makes for us. `int` does not have `opAssign`, so we can't forward to that, thus again causing possible issues. In fact, `int` doesn't have any members at all, if you ask `__traits(allMembers)`. Thus, any member whatsoever in `Typedef!int` could be problematic in generic code. I hope we can agree that `Typedef!int` needs to have at least one member.
Actually, we can get rid of all fields by (ab)using `align` and some creative casting:
template Typedef(T) {
align ((T.sizeof + 1) & -2) // powers of 2 only
struct Typedef {
ref auto _payload() {
import std.typecons : TypedefImpl = Typedef;
return *cast(TypedefImpl!T*)&this;
}
alias _payload this;
// alias all members of the wrapped type
static if (__traits(compiles, __traits(allMembers, T)))
static foreach (e; __traits(allMembers, T))
mixin("alias _payload."~e~" "~e~";");
}
}
This still leaves us with a single method (`_payload()`) that I haven't found a way to get rid of yet. Also, it's hella ugly.
Comment #5 by robert.schadek — 2024-12-01T16:35:24Z