-
-
Notifications
You must be signed in to change notification settings - Fork 763
Add std.typecons.Rebindable!struct #4363
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d5093d1
f9599c6
d6523f1
b828237
90a1306
08a95dc
49b8f36
85ebede
3caca47
d128695
bf3bc5b
45eee71
ac5abae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1666,11 +1666,11 @@ Convenience function for creating a $(D Rebindable) using automatic type | |
| inference. | ||
|
|
||
| Params: | ||
| obj = A reference to an object, interface, associative array, or an array slice | ||
| to initialize the `Rebindable` with. | ||
| obj = A reference to an object, interface, associative array, or an array slice. | ||
| s = Struct instance. | ||
|
|
||
| Returns: | ||
| A newly constructed `Rebindable` initialized with the given reference. | ||
| A newly constructed `Rebindable` initialized with the given data. | ||
| */ | ||
| Rebindable!T rebindable(T)(T obj) | ||
| if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) | ||
|
|
@@ -1680,6 +1680,17 @@ Rebindable!T rebindable(T)(T obj) | |
| return ret; | ||
| } | ||
|
|
||
| /// ditto | ||
| Rebindable!S rebindable(S)(S s) | ||
| if (is(S == struct) && !isInstanceOf!(Rebindable, S)) | ||
| { | ||
| // workaround for rebindableS = rebindable(s) | ||
| static if (isMutable!S) | ||
| return s; | ||
| else | ||
| return Rebindable!S(s); | ||
| } | ||
|
|
||
| /** | ||
| This function simply returns the $(D Rebindable) object passed in. It's useful | ||
| in generic programming cases when a given object may be either a regular | ||
|
|
@@ -1787,6 +1798,220 @@ unittest | |
| assert(rebindable(pr3341_aa)[321] == 543); | ||
| } | ||
|
|
||
| /** Models safe reassignment of otherwise constant struct instances. | ||
| * | ||
| * A struct with a field of reference type cannot be assigned to a constant | ||
| * struct of the same type. `Rebindable!(const S)` allows assignment to | ||
| * a `const S` while enforcing only constant access to its fields. | ||
| * | ||
| * `Rebindable!(immutable S)` does the same but field access may create a | ||
| * temporary copy of `S` in order to enforce _true immutability. | ||
| */ | ||
| template Rebindable(S) | ||
| if (is(S == struct)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be awesome if you could put all Rebindable types under this template as sth. like |
||
| { | ||
| static if (isMutable!S) | ||
| alias Rebindable = S; | ||
| else | ||
| struct Rebindable | ||
| { | ||
| private: | ||
| // mutPayload's pointers must be treated as tail const | ||
| void[S.sizeof] mutPayload; | ||
|
|
||
| void emplace(ref S s) | ||
| { | ||
| import std.conv : emplace; | ||
| static if (__traits(compiles, () @safe {S tmp = s;})) | ||
| () @trusted {emplace!S(mutPayload, s);}(); | ||
| else | ||
| emplace!S(mutPayload, s); | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about pushing this
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's only safe if |
||
|
|
||
| public: | ||
| this()(auto ref S s) | ||
| { | ||
| emplace(s); | ||
| } | ||
|
|
||
| // immutable S cannot be passed to auto ref S above | ||
| static if (!is(S == immutable)) | ||
| this()(immutable S s) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that's what
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't work. I'm a bit rusty on this PR but I remember struggling with compile errors - |
||
| { | ||
| emplace(s); | ||
| } | ||
|
|
||
| void opAssign()(auto ref S s) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for Rebindable!(immutable S) ri = S(new int);
auto ri2 = ri;this yields conflicting overloads.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Works for me - I added a test for this, let's see if the autotester passes. |
||
| { | ||
| movePayload; | ||
| emplace(s); | ||
| } | ||
|
|
||
| static if (!is(S == immutable)) | ||
| void opAssign()(immutable S s) | ||
| { | ||
| movePayload; | ||
| emplace(s); | ||
| } | ||
|
|
||
| void opAssign(Rebindable other) | ||
| { | ||
| this = other.trustedPayload; | ||
| } | ||
|
|
||
| // must not escape when S is immutable | ||
| private | ||
| ref trustedPayload() @trusted | ||
| { | ||
| return *cast(S*)mutPayload.ptr; | ||
| } | ||
|
|
||
| static if (!is(S == immutable)) | ||
| ref S Rebindable_getRef() @property | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be private
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't be because it's used for |
||
| { | ||
| // payload exposed as const ref when S is const | ||
| return trustedPayload; | ||
| } | ||
|
|
||
| static if (is(S == immutable)) | ||
| S Rebindable_get() @property | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto ( |
||
| { | ||
| // we return a copy for immutable S | ||
| return trustedPayload; | ||
| } | ||
|
|
||
| static if (is(S == immutable)) | ||
| alias Rebindable_get this; | ||
| else | ||
| alias Rebindable_getRef this; | ||
|
|
||
| private | ||
| auto movePayload() @trusted | ||
| { | ||
| import std.algorithm : move; | ||
| return cast(S)move(*cast(Unqual!S*)mutPayload.ptr); | ||
| } | ||
|
|
||
| ~this() | ||
| { | ||
| // call destructor with proper constness | ||
| movePayload; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// | ||
| @safe unittest | ||
| { | ||
| static struct S | ||
| { | ||
| int* ptr; | ||
| } | ||
| S s = S(new int); | ||
|
|
||
| const cs = s; | ||
| // Can't assign s.ptr to cs.ptr | ||
| static assert(!__traits(compiles, {s = cs;})); | ||
|
|
||
| Rebindable!(const S) rs = s; | ||
| assert(rs.ptr is s.ptr); | ||
| // rs.ptr is const | ||
| static assert(!__traits(compiles, {rs.ptr = null;})); | ||
|
|
||
| // Can't assign s.ptr to rs.ptr | ||
| static assert(!__traits(compiles, {s = rs;})); | ||
|
|
||
| const S cs2 = rs; | ||
| // Rebind rs | ||
| rs = cs2; | ||
| rs = S(); | ||
| assert(rs.ptr is null); | ||
| } | ||
|
|
||
| // Test Rebindable!immutable | ||
| @safe unittest | ||
| { | ||
| static struct S | ||
| { | ||
| int* ptr; | ||
| } | ||
| S s = S(new int); | ||
|
|
||
| Rebindable!(immutable S) ri = S(new int); | ||
| assert(ri.ptr !is null); | ||
| static assert(!__traits(compiles, {ri.ptr = null;})); | ||
|
|
||
| // ri is not compatible with mutable S | ||
| static assert(!__traits(compiles, {s = ri;})); | ||
| static assert(!__traits(compiles, {ri = s;})); | ||
|
|
||
| auto ri2 = ri; | ||
| assert(ri2.ptr == ri.ptr); | ||
|
|
||
| const S cs3 = ri; | ||
| static assert(!__traits(compiles, {ri = cs3;})); | ||
|
|
||
| immutable S si = ri; | ||
| // Rebind ri | ||
| ri = si; | ||
| ri = S(); | ||
| assert(ri.ptr is null); | ||
|
|
||
| // Test RB!immutable -> RB!const | ||
| Rebindable!(const S) rc = ri; | ||
| assert(rc.ptr is null); | ||
| ri = S(new int); | ||
| rc = ri; | ||
| assert(rc.ptr !is null); | ||
|
|
||
| // test rebindable, opAssign | ||
| rc.destroy; | ||
| assert(rc.ptr is null); | ||
| rc = rebindable(cs3); | ||
| rc = rebindable(si); | ||
| assert(rc.ptr !is null); | ||
|
|
||
| ri.destroy; | ||
| assert(ri.ptr is null); | ||
| ri = rebindable(si); | ||
| assert(ri.ptr !is null); | ||
| } | ||
|
|
||
| // Test Rebindable!mutable | ||
| @safe unittest | ||
| { | ||
| static struct S | ||
| { | ||
| int* ptr; | ||
| } | ||
| S s; | ||
|
|
||
| Rebindable!S rs = s; | ||
| static assert(is(typeof(rs) == S)); | ||
| rs = rebindable(S()); | ||
| } | ||
|
|
||
| // Test disabled default ctor | ||
| unittest | ||
| { | ||
| static struct ND | ||
| { | ||
| int i; | ||
| @disable this(); | ||
| this(int i) inout {this.i = i;} | ||
| } | ||
| static assert(!__traits(compiles, Rebindable!ND())); | ||
|
|
||
| Rebindable!(const ND) rb = const ND(1); | ||
| assert(rb.i == 1); | ||
| rb = immutable ND(2); | ||
| assert(rb.i == 2); | ||
| rb = rebindable(const ND(3)); | ||
| assert(rb.i == 3); | ||
| static assert(!__traits(compiles, rb.i++)); | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| Similar to $(D Rebindable!(T)) but strips all qualifiers from the reference as | ||
| opposed to just constness / immutability. Primary intended use case is with | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Phobos style is to avoid such type overloads and hide them as implementation details.