Skip to content
Closed
231 changes: 228 additions & 3 deletions std/typecons.d
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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))
Copy link
Copy Markdown
Contributor

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.

{
// 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
Expand Down Expand Up @@ -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))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 __traits(isSame, TemplateOf!(typeof(ri2)), Rebindable)
would work. Currently Rebindable references to the first symbol - the one for classes etc.

{
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);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about pushing this @safe improvement to std.conv.emplace?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's only safe if S.~this is run when mutPayload goes out of scope, which emplace can't enforce.


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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's what inout was made for ;-) -> inout auto ref S s

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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 - inout triggers them.

{
emplace(s);
}

void opAssign()(auto ref S s)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be private

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't be because it's used for alias this.

{
// payload exposed as const ref when S is const
return trustedPayload;
}

static if (is(S == immutable))
S Rebindable_get() @property
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto (private)

{
// 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
Expand Down