Issue 20117 - std.typecons.Typedef has opCmp when base type does not
Summary: std.typecons.Typedef has opCmp when base type does not
Status: NEW
Alias: None
Product: D
Classification: Unclassified
Component: phobos (show other issues)
Version: D2
Hardware: All All
: P3 normal
Assignee: No Owner
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2019-08-08 16:51 UTC by Atila Neves
Modified: 2024-12-01 16:35 UTC (History)
2 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description Atila Neves 2019-08-08 16:51:54 UTC
----------------------------------
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 Simen Kjaeraas 2019-08-09 07:14:08 UTC
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 Simen Kjaeraas 2019-08-09 07:51:39 UTC
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 Atila Neves 2019-08-09 08:21:20 UTC
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 Simen Kjaeraas 2019-08-09 11:37:22 UTC
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 dlangBugzillaToGithub 2024-12-01 16:35:24 UTC
THIS ISSUE HAS BEEN MOVED TO GITHUB

https://github.com/dlang/phobos/issues/10382

DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB