typecons.Variant: Make public. Fix #73

This commit is contained in:
Eugen Wissner 2019-01-31 06:14:09 +01:00
parent 5453f6417f
commit c1535e8752
2 changed files with 205 additions and 16 deletions

View File

@ -28,6 +28,11 @@ matrix:
d: dmd-$LATEST d: dmd-$LATEST
env: DDOC=true env: DDOC=true
os: linux os: linux
allow_failures:
- name: D-Scanner
d: dmd-$LATEST
env: DSCANNER=0.5.11
os: linux
addons: addons:
apt: apt:

View File

@ -18,6 +18,7 @@
module tanya.typecons; module tanya.typecons;
import tanya.algorithm.mutation; import tanya.algorithm.mutation;
import tanya.conv;
import tanya.format; import tanya.format;
import tanya.functional; import tanya.functional;
import tanya.meta.metafunction; import tanya.meta.metafunction;
@ -562,20 +563,18 @@ Option!T option(T)()
assert(option(5) == 5); assert(option(5) == 5);
} }
private struct VariantAccessorInfo
{
string accessor;
size_t tag;
}
/** /**
* Tagged union. * Type that can hold one of the types listed as its template parameters.
*
* $(D_PSYMBOL Variant) is a type similar to $(D_KEYWORD union), but
* $(D_PSYMBOL Variant) keeps track of the actually used type and throws an
* assertion error when trying to access an invalid type at runtime.
* *
* Params: * Params:
* Specs = Types of the union members. * Specs = Types this $(D_SPYBMOL Variant) can hold.
*/ */
package (tanya) template Variant(Specs...) template Variant(Specs...)
if (isTypeTuple!Specs) if (isTypeTuple!Specs && NoDuplicates!Specs.length == Specs.length)
{ {
union AlignedUnion(Args...) union AlignedUnion(Args...)
{ {
@ -589,6 +588,12 @@ if (isTypeTuple!Specs)
} }
} }
private struct VariantAccessorInfo
{
string accessor;
ptrdiff_t tag;
}
template accessor(T, Union) template accessor(T, Union)
{ {
enum VariantAccessorInfo info = accessorImpl!(T, Union, 1); enum VariantAccessorInfo info = accessorImpl!(T, Union, 1);
@ -616,23 +621,84 @@ if (isTypeTuple!Specs)
private ptrdiff_t tag = -1; private ptrdiff_t tag = -1;
private AlignedUnion!Types values; private AlignedUnion!Types values;
this(T)(auto ref T value) /**
* Constructs this $(D_PSYMBOL Variant) with one of the types supported
* in it.
*
* Params:
* T = Type of the initial value.
* value = Initial value.
*/
this(T)(ref T value)
if (canFind!(T, Types)) if (canFind!(T, Types))
{ {
opAssign!T(forward!value); copyAssign!T(value);
} }
/// ditto
this(T)(T value)
if (canFind!(T, Types))
{
moveAssign!T(value);
}
~this()
{
reset();
}
this(this)
{
alias pred(U) = hasElaborateCopyConstructor!(U.Seq[1]);
static foreach (Type; Filter!(pred, Enumerate!Types))
{
if (this.tag == Type.Seq[0])
{
get!(Type.Seq[1]).__postblit();
}
}
}
/**
* Tells whether this $(D_PSYMBOL Variant) was initialized.
*
* Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Variant) contains a
* value, $(D_KEYWORD false) otherwise.
*/
bool hasValue() const bool hasValue() const
{ {
return this.tag != -1; return this.tag != -1;
} }
/**
* Tells whether this $(D_PSYMBOL Variant) holds currently a value of
* type $(D_PARAM T).
*
* Params:
* T = Examined type.
*
* Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Variant) currently
* contains a value of type $(D_PARAM T), $(D_KEYWORD false)
* otherwise.
*/
bool peek(T)() const bool peek(T)() const
if (canFind!(T, Types)) if (canFind!(T, Types))
{ {
return this.tag == staticIndexOf!(T, Types); return this.tag == staticIndexOf!(T, Types);
} }
/**
* Returns the underlying value, assuming it is of the type $(D_PARAM T).
*
* Params:
* T = Type of the value should be returned.
*
* Returns: The underyling value.
*
* Precondition: The $(D_PSYMBOL Variant) has a value.
*
* See_Also: $(D_PSYMBOL peek), $(D_PSYMBOL hasValue).
*/
ref inout(T) get(T)() inout ref inout(T) get(T)() inout
if (canFind!(T, Types)) if (canFind!(T, Types))
in (this.tag == staticIndexOf!(T, Types), "Variant isn't initialized") in (this.tag == staticIndexOf!(T, Types), "Variant isn't initialized")
@ -640,15 +706,71 @@ if (isTypeTuple!Specs)
mixin("return " ~ accessor!(T, AlignedUnion!Types).accessor ~ ";"); mixin("return " ~ accessor!(T, AlignedUnion!Types).accessor ~ ";");
} }
typeof(this) opAssign(T)(auto ref T value) /**
* Reassigns the value.
*
* Params:
* T = Type of the new value
* that = New value.
*
* Returns: $(D_KEYWORD this).
*/
ref typeof(this) opAssign(T)(T that)
if (canFind!(T, Types)) if (canFind!(T, Types))
{
reset();
return moveAssign!T(that);
}
/// ditto
ref typeof(this) opAssign(T)(ref T that)
if (canFind!(T, Types))
{
reset();
return copyAssign!T(that);
}
private ref typeof(this) moveAssign(T)(ref T that) @trusted
{ {
this.tag = staticIndexOf!(T, Types); this.tag = staticIndexOf!(T, Types);
mixin(accessor!(T, AlignedUnion!Types).accessor ~ " = forward!value;");
enum string accessorMixin = accessor!(T, AlignedUnion!Types).accessor;
moveEmplace(that, mixin(accessorMixin));
return this; return this;
} }
TypeInfo type() private ref typeof(this) copyAssign(T)(ref T that)
{
this.tag = staticIndexOf!(T, Types);
enum string accessorMixin = accessor!(T, AlignedUnion!Types).accessor;
emplace!T((() @trusted => (&mixin(accessorMixin))[0 .. 1])(), that);
return this;
}
private void reset()
{
alias pred(U) = hasElaborateDestructor!(U.Seq[1]);
static foreach (Type; Filter!(pred, Enumerate!Types))
{
if (this.tag == Type.Seq[0])
{
destroy(get!(Type.Seq[1]));
}
}
}
/**
* Returns $(D_PSYMBOL TypeInfo) corresponding to the current type.
*
* If this $(D_PSYMBOL Variant) isn't initialized, return
* $(D_KEYWORD null).
*
* Returns: $(D_PSYMBOL TypeInfo) of the current type.
*/
@property TypeInfo type()
{ {
static foreach (i, Type; Types) static foreach (i, Type; Types)
{ {
@ -657,9 +779,29 @@ if (isTypeTuple!Specs)
return typeid(Type); return typeid(Type);
} }
} }
assert(false, "Variant isn't initialized"); return null;
} }
/**
* Compares this $(D_PSYMBOL Variant) with another one with the same
* specification for equality.
*
* $(UL
* $(LI If both hold values of the same type, these values are
* compared.)
* $(LI If they hold values of different types, then the
* $(D_PSYMBOL Variant)s aren't equal.)
* $(LI If only one of them is initialized but another one not, they
* aren't equal.)
* $(LI If neither of them is initialized, they are equal.)
* )
*
* Params:
* that = The $(D_PSYMBOL Variant) to compare with.
*
* Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Variant) is equal to
* $(D_PARAM that), $(D_KEYWORD false) otherwise.
*/
bool opEquals()(auto ref inout Variant that) inout bool opEquals()(auto ref inout Variant that) inout
{ {
if (this.tag != that.tag) if (this.tag != that.tag)
@ -678,6 +820,18 @@ if (isTypeTuple!Specs)
} }
} }
///
@nogc nothrow pure @safe unittest
{
Variant!(int, double) variant = 5;
assert(variant.peek!int);
assert(variant.get!int == 5);
variant = 5.4;
assert(!variant.peek!int);
assert(variant.get!double == 5.4);
}
@nogc nothrow pure @safe unittest @nogc nothrow pure @safe unittest
{ {
Variant!(int, double) variant; Variant!(int, double) variant;
@ -748,3 +902,33 @@ if (isTypeTuple!Specs)
Variant!(int, double) variant1, variant2; Variant!(int, double) variant1, variant2;
assert(variant1 == variant2); assert(variant1 == variant2);
} }
// Calls postblit constructor of the active type
@nogc nothrow pure @safe unittest
{
static struct S
{
bool called;
this(this)
{
this.called = true;
}
}
Variant!(int, S) variant1 = S();
auto variant2 = variant1;
assert(variant2.get!S.called);
}
// Variant.type is null if the Variant doesn't have a value
@nogc nothrow pure @safe unittest
{
Variant!(int, uint) variant;
assert(variant.type is null);
}
// Variant can contain only distinct types
@nogc nothrow pure @safe unittest
{
static assert(!is(Variant!(int, int)));
}