| // Written in the D programming language. |
| |
| /** |
| This module implements a variety of type constructors, i.e., templates |
| that allow construction of new, useful general-purpose types. |
| |
| $(SCRIPT inhibitQuickIndex = 1;) |
| $(DIVC quickindex, |
| $(BOOKTABLE, |
| $(TR $(TH Category) $(TH Symbols)) |
| $(TR $(TD Tuple) $(TD |
| $(LREF isTuple) |
| $(LREF Tuple) |
| $(LREF tuple) |
| $(LREF reverse) |
| )) |
| $(TR $(TD Flags) $(TD |
| $(LREF BitFlags) |
| $(LREF isBitFlagEnum) |
| $(LREF Flag) |
| $(LREF No) |
| $(LREF Yes) |
| )) |
| $(TR $(TD Memory allocation) $(TD |
| $(LREF RefCounted) |
| $(LREF refCounted) |
| $(LREF RefCountedAutoInitialize) |
| $(LREF scoped) |
| $(LREF Unique) |
| )) |
| $(TR $(TD Code generation) $(TD |
| $(LREF AutoImplement) |
| $(LREF BlackHole) |
| $(LREF generateAssertTrap) |
| $(LREF generateEmptyFunction) |
| $(LREF WhiteHole) |
| )) |
| $(TR $(TD Nullable) $(TD |
| $(LREF Nullable) |
| $(LREF nullable) |
| $(LREF NullableRef) |
| $(LREF nullableRef) |
| )) |
| $(TR $(TD Proxies) $(TD |
| $(LREF Proxy) |
| $(LREF rebindable) |
| $(LREF Rebindable) |
| $(LREF ReplaceType) |
| $(LREF unwrap) |
| $(LREF wrap) |
| )) |
| $(TR $(TD Types) $(TD |
| $(LREF alignForSize) |
| $(LREF Ternary) |
| $(LREF Typedef) |
| $(LREF TypedefType) |
| $(LREF UnqualRef) |
| )) |
| )) |
| |
| Copyright: Copyright the respective authors, 2008- |
| License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). |
| Source: $(PHOBOSSRC std/typecons.d) |
| Authors: $(HTTP erdani.org, Andrei Alexandrescu), |
| $(HTTP bartoszmilewski.wordpress.com, Bartosz Milewski), |
| Don Clugston, |
| Shin Fujishiro, |
| Kenji Hara |
| */ |
| module std.typecons; |
| |
| import std.format.spec : singleSpec, FormatSpec; |
| import std.format.write : formatValue; |
| import std.meta : AliasSeq, allSatisfy; |
| import std.range.primitives : isOutputRange; |
| import std.traits; |
| import std.internal.attributes : betterC; |
| |
| /// Value tuples |
| @safe unittest |
| { |
| alias Coord = Tuple!(int, "x", int, "y", int, "z"); |
| Coord c; |
| c[1] = 1; // access by index |
| c.z = 1; // access by given name |
| assert(c == Coord(0, 1, 1)); |
| |
| // names can be omitted, types can be mixed |
| alias DictEntry = Tuple!(string, int); |
| auto dict = DictEntry("seven", 7); |
| |
| // element types can be inferred |
| assert(tuple(2, 3, 4)[1] == 3); |
| // type inference works with names too |
| auto tup = tuple!("x", "y", "z")(2, 3, 4); |
| assert(tup.y == 3); |
| } |
| |
| /// Rebindable references to const and immutable objects |
| @safe unittest |
| { |
| class Widget |
| { |
| void foo() const @safe {} |
| } |
| const w1 = new Widget, w2 = new Widget; |
| w1.foo(); |
| // w1 = w2 would not work; can't rebind const object |
| |
| auto r = Rebindable!(const Widget)(w1); |
| // invoke method as if r were a Widget object |
| r.foo(); |
| // rebind r to refer to another object |
| r = w2; |
| } |
| |
| /** |
| Encapsulates unique ownership of a resource. |
| |
| When a `Unique!T` goes out of scope it will call `destroy` |
| on the resource `T` that it manages, unless it is transferred. |
| One important consequence of `destroy` is that it will call the |
| destructor of the resource `T`. GC-managed references are not |
| guaranteed to be valid during a destructor call, but other members of |
| `T`, such as file handles or pointers to `malloc` memory, will |
| still be valid during the destructor call. This allows the resource |
| `T` to deallocate or clean up any non-GC resources. |
| |
| If it is desirable to persist a `Unique!T` outside of its original |
| scope, then it can be transferred. The transfer can be explicit, by |
| calling `release`, or implicit, when returning Unique from a |
| function. The resource `T` can be a polymorphic class object or |
| instance of an interface, in which case Unique behaves polymorphically |
| too. |
| |
| If `T` is a value type, then `Unique!T` will be implemented |
| as a reference to a `T`. |
| */ |
| struct Unique(T) |
| { |
| /** Represents a reference to `T`. Resolves to `T*` if `T` is a value type. */ |
| static if (is(T == class) || is(T == interface)) |
| alias RefT = T; |
| else |
| alias RefT = T*; |
| |
| public: |
| // Deferred in case we get some language support for checking uniqueness. |
| version (None) |
| /** |
| Allows safe construction of `Unique`. It creates the resource and |
| guarantees unique ownership of it (unless `T` publishes aliases of |
| `this`). |
| Note: Nested structs/classes cannot be created. |
| Params: |
| args = Arguments to pass to `T`'s constructor. |
| --- |
| static class C {} |
| auto u = Unique!(C).create(); |
| --- |
| */ |
| static Unique!T create(A...)(auto ref A args) |
| if (__traits(compiles, new T(args))) |
| { |
| Unique!T u; |
| u._p = new T(args); |
| return u; |
| } |
| |
| /** |
| Constructor that takes an rvalue. |
| It will ensure uniqueness, as long as the rvalue |
| isn't just a view on an lvalue (e.g., a cast). |
| Typical usage: |
| ---- |
| Unique!Foo f = new Foo; |
| ---- |
| */ |
| this(RefT p) |
| { |
| _p = p; |
| } |
| /** |
| Constructor that takes an lvalue. It nulls its source. |
| The nulling will ensure uniqueness as long as there |
| are no previous aliases to the source. |
| */ |
| this(ref RefT p) |
| { |
| _p = p; |
| p = null; |
| assert(p is null); |
| } |
| /** |
| Constructor that takes a `Unique` of a type that is convertible to our type. |
| |
| Typically used to transfer a `Unique` rvalue of derived type to |
| a `Unique` of base type. |
| Example: |
| --- |
| class C : Object {} |
| |
| Unique!C uc = new C; |
| Unique!Object uo = uc.release; |
| --- |
| */ |
| this(U)(Unique!U u) |
| if (is(u.RefT:RefT)) |
| { |
| _p = u._p; |
| u._p = null; |
| } |
| |
| /// Transfer ownership from a `Unique` of a type that is convertible to our type. |
| void opAssign(U)(Unique!U u) |
| if (is(u.RefT:RefT)) |
| { |
| // first delete any resource we own |
| destroy(this); |
| _p = u._p; |
| u._p = null; |
| } |
| |
| ~this() |
| { |
| if (_p !is null) |
| { |
| destroy(_p); |
| _p = null; |
| } |
| } |
| |
| /** Returns whether the resource exists. */ |
| @property bool isEmpty() const |
| { |
| return _p is null; |
| } |
| /** Transfer ownership to a `Unique` rvalue. Nullifies the current contents. |
| Same as calling std.algorithm.move on it. |
| */ |
| Unique release() |
| { |
| import std.algorithm.mutation : move; |
| return this.move; |
| } |
| |
| /** Forwards member access to contents. */ |
| mixin Proxy!_p; |
| |
| /** |
| Postblit operator is undefined to prevent the cloning of `Unique` objects. |
| */ |
| @disable this(this); |
| |
| private: |
| RefT _p; |
| } |
| |
| /// |
| @safe unittest |
| { |
| static struct S |
| { |
| int i; |
| this(int i){this.i = i;} |
| } |
| Unique!S produce() |
| { |
| // Construct a unique instance of S on the heap |
| Unique!S ut = new S(5); |
| // Implicit transfer of ownership |
| return ut; |
| } |
| // Borrow a unique resource by ref |
| void increment(ref Unique!S ur) |
| { |
| ur.i++; |
| } |
| void consume(Unique!S u2) |
| { |
| assert(u2.i == 6); |
| // Resource automatically deleted here |
| } |
| Unique!S u1; |
| assert(u1.isEmpty); |
| u1 = produce(); |
| increment(u1); |
| assert(u1.i == 6); |
| //consume(u1); // Error: u1 is not copyable |
| // Transfer ownership of the resource |
| consume(u1.release); |
| assert(u1.isEmpty); |
| } |
| |
| @system unittest |
| { |
| // test conversion to base ref |
| int deleted = 0; |
| class C |
| { |
| ~this(){deleted++;} |
| } |
| // constructor conversion |
| Unique!Object u = Unique!C(new C); |
| static assert(!__traits(compiles, {u = new C;})); |
| assert(!u.isEmpty); |
| destroy(u); |
| assert(deleted == 1); |
| |
| Unique!C uc = new C; |
| static assert(!__traits(compiles, {Unique!Object uo = uc;})); |
| Unique!Object uo = new C; |
| // opAssign conversion, deleting uo resource first |
| uo = uc.release; |
| assert(uc.isEmpty); |
| assert(!uo.isEmpty); |
| assert(deleted == 2); |
| } |
| |
| @system unittest |
| { |
| class Bar |
| { |
| ~this() { debug(Unique) writeln(" Bar destructor"); } |
| int val() const { return 4; } |
| } |
| alias UBar = Unique!(Bar); |
| UBar g(UBar u) |
| { |
| debug(Unique) writeln("inside g"); |
| return u.release; |
| } |
| auto ub = UBar(new Bar); |
| assert(!ub.isEmpty); |
| assert(ub.val == 4); |
| static assert(!__traits(compiles, {auto ub3 = g(ub);})); |
| auto ub2 = g(ub.release); |
| assert(ub.isEmpty); |
| assert(!ub2.isEmpty); |
| } |
| |
| @system unittest |
| { |
| interface Bar |
| { |
| int val() const; |
| } |
| class BarImpl : Bar |
| { |
| static int count; |
| this() |
| { |
| count++; |
| } |
| ~this() |
| { |
| count--; |
| } |
| int val() const { return 4; } |
| } |
| alias UBar = Unique!Bar; |
| UBar g(UBar u) |
| { |
| debug(Unique) writeln("inside g"); |
| return u.release; |
| } |
| void consume(UBar u) |
| { |
| assert(u.val() == 4); |
| // Resource automatically deleted here |
| } |
| auto ub = UBar(new BarImpl); |
| assert(BarImpl.count == 1); |
| assert(!ub.isEmpty); |
| assert(ub.val == 4); |
| static assert(!__traits(compiles, {auto ub3 = g(ub);})); |
| auto ub2 = g(ub.release); |
| assert(ub.isEmpty); |
| assert(!ub2.isEmpty); |
| consume(ub2.release); |
| assert(BarImpl.count == 0); |
| } |
| |
| @safe unittest |
| { |
| struct Foo |
| { |
| ~this() { } |
| int val() const { return 3; } |
| @disable this(this); |
| } |
| alias UFoo = Unique!(Foo); |
| |
| UFoo f(UFoo u) |
| { |
| return u.release; |
| } |
| |
| auto uf = UFoo(new Foo); |
| assert(!uf.isEmpty); |
| assert(uf.val == 3); |
| static assert(!__traits(compiles, {auto uf3 = f(uf);})); |
| auto uf2 = f(uf.release); |
| assert(uf.isEmpty); |
| assert(!uf2.isEmpty); |
| } |
| |
| // ensure Unique behaves correctly through const access paths |
| @system unittest |
| { |
| struct Bar {int val;} |
| struct Foo |
| { |
| Unique!Bar bar = new Bar; |
| } |
| |
| Foo foo; |
| foo.bar.val = 6; |
| const Foo* ptr = &foo; |
| static assert(is(typeof(ptr) == const(Foo*))); |
| static assert(is(typeof(ptr.bar) == const(Unique!Bar))); |
| static assert(is(typeof(ptr.bar.val) == const(int))); |
| assert(ptr.bar.val == 6); |
| foo.bar.val = 7; |
| assert(ptr.bar.val == 7); |
| } |
| |
| // Used in Tuple.toString |
| private template sharedToString(alias field) |
| if (is(typeof(field) == shared)) |
| { |
| static immutable sharedToString = typeof(field).stringof; |
| } |
| |
| private template sharedToString(alias field) |
| if (!is(typeof(field) == shared)) |
| { |
| alias sharedToString = field; |
| } |
| |
| private enum bool distinctFieldNames(names...) = __traits(compiles, |
| { |
| static foreach (__name; names) |
| static if (is(typeof(__name) : string)) |
| mixin("enum int " ~ __name ~ " = 0;"); |
| }); |
| |
| @safe unittest |
| { |
| static assert(!distinctFieldNames!(string, "abc", string, "abc")); |
| static assert(distinctFieldNames!(string, "abc", int, "abd")); |
| static assert(!distinctFieldNames!(int, "abc", string, "abd", int, "abc")); |
| // https://issues.dlang.org/show_bug.cgi?id=19240 |
| static assert(!distinctFieldNames!(int, "int")); |
| } |
| |
| |
| // Parse (type,name) pairs (FieldSpecs) out of the specified |
| // arguments. Some fields would have name, others not. |
| private template parseSpecs(Specs...) |
| { |
| static if (Specs.length == 0) |
| { |
| alias parseSpecs = AliasSeq!(); |
| } |
| else static if (is(Specs[0])) |
| { |
| static if (is(typeof(Specs[1]) : string)) |
| { |
| alias parseSpecs = |
| AliasSeq!(FieldSpec!(Specs[0 .. 2]), |
| parseSpecs!(Specs[2 .. $])); |
| } |
| else |
| { |
| alias parseSpecs = |
| AliasSeq!(FieldSpec!(Specs[0]), |
| parseSpecs!(Specs[1 .. $])); |
| } |
| } |
| else |
| { |
| static assert(0, "Attempted to instantiate Tuple with an " |
| ~"invalid argument: "~ Specs[0].stringof); |
| } |
| } |
| |
| private template FieldSpec(T, string s = "") |
| { |
| alias Type = T; |
| alias name = s; |
| } |
| |
| // Used with staticMap. |
| private alias extractType(alias spec) = spec.Type; |
| private alias extractName(alias spec) = spec.name; |
| private template expandSpec(alias spec) |
| { |
| static if (spec.name.length == 0) |
| alias expandSpec = AliasSeq!(spec.Type); |
| else |
| alias expandSpec = AliasSeq!(spec.Type, spec.name); |
| } |
| |
| |
| private enum areCompatibleTuples(Tup1, Tup2, string op) = |
| isTuple!(OriginalType!Tup2) && Tup1.Types.length == Tup2.Types.length && is(typeof( |
| (ref Tup1 tup1, ref Tup2 tup2) |
| { |
| static foreach (i; 0 .. Tup1.Types.length) |
| {{ |
| auto lhs = typeof(tup1.field[i]).init; |
| auto rhs = typeof(tup2.field[i]).init; |
| static if (op == "=") |
| lhs = rhs; |
| else |
| auto result = mixin("lhs "~op~" rhs"); |
| }} |
| })); |
| |
| private enum areBuildCompatibleTuples(Tup1, Tup2) = |
| isTuple!Tup2 && Tup1.Types.length == Tup2.Types.length && is(typeof( |
| { |
| static foreach (i; 0 .. Tup1.Types.length) |
| static assert(isBuildable!(Tup1.Types[i], Tup2.Types[i])); |
| })); |
| |
| // Returns `true` iff a `T` can be initialized from a `U`. |
| private enum isBuildable(T, U) = is(typeof( |
| { |
| U u = U.init; |
| T t = u; |
| })); |
| // Helper for partial instantiation |
| private template isBuildableFrom(U) |
| { |
| enum isBuildableFrom(T) = isBuildable!(T, U); |
| } |
| |
| |
| /** |
| _Tuple of values, for example $(D Tuple!(int, string)) is a record that |
| stores an `int` and a `string`. `Tuple` can be used to bundle |
| values together, notably when returning multiple values from a |
| function. If `obj` is a `Tuple`, the individual members are |
| accessible with the syntax `obj[0]` for the first field, `obj[1]` |
| for the second, and so on. |
| |
| See_Also: $(LREF tuple). |
| |
| Params: |
| Specs = A list of types (and optionally, member names) that the `Tuple` contains. |
| */ |
| template Tuple(Specs...) |
| if (distinctFieldNames!(Specs)) |
| { |
| import std.meta : staticMap; |
| |
| alias fieldSpecs = parseSpecs!Specs; |
| |
| // Generates named fields as follows: |
| // alias name_0 = Identity!(field[0]); |
| // alias name_1 = Identity!(field[1]); |
| // : |
| // NOTE: field[k] is an expression (which yields a symbol of a |
| // variable) and can't be aliased directly. |
| enum injectNamedFields = () |
| { |
| string decl = ""; |
| static foreach (i, val; fieldSpecs) |
| {{ |
| immutable si = i.stringof; |
| decl ~= "alias _" ~ si ~ " = Identity!(field[" ~ si ~ "]);"; |
| if (val.name.length != 0) |
| { |
| decl ~= "alias " ~ val.name ~ " = _" ~ si ~ ";"; |
| } |
| }} |
| return decl; |
| }; |
| |
| // Returns Specs for a subtuple this[from .. to] preserving field |
| // names if any. |
| alias sliceSpecs(size_t from, size_t to) = |
| staticMap!(expandSpec, fieldSpecs[from .. to]); |
| |
| struct Tuple |
| { |
| /** |
| * The types of the `Tuple`'s components. |
| */ |
| alias Types = staticMap!(extractType, fieldSpecs); |
| |
| private alias _Fields = Specs; |
| |
| /// |
| static if (Specs.length == 0) @safe unittest |
| { |
| import std.meta : AliasSeq; |
| alias Fields = Tuple!(int, "id", string, float); |
| static assert(is(Fields.Types == AliasSeq!(int, string, float))); |
| } |
| |
| /** |
| * The names of the `Tuple`'s components. Unnamed fields have empty names. |
| */ |
| alias fieldNames = staticMap!(extractName, fieldSpecs); |
| |
| /// |
| static if (Specs.length == 0) @safe unittest |
| { |
| import std.meta : AliasSeq; |
| alias Fields = Tuple!(int, "id", string, float); |
| static assert(Fields.fieldNames == AliasSeq!("id", "", "")); |
| } |
| |
| /** |
| * Use `t.expand` for a `Tuple` `t` to expand it into its |
| * components. The result of `expand` acts as if the `Tuple`'s components |
| * were listed as a list of values. (Ordinarily, a `Tuple` acts as a |
| * single value.) |
| */ |
| Types expand; |
| mixin(injectNamedFields()); |
| |
| /// |
| static if (Specs.length == 0) @safe unittest |
| { |
| auto t1 = tuple(1, " hello ", 'a'); |
| assert(t1.toString() == `Tuple!(int, string, char)(1, " hello ", 'a')`); |
| |
| void takeSeveralTypes(int n, string s, bool b) |
| { |
| assert(n == 4 && s == "test" && b == false); |
| } |
| |
| auto t2 = tuple(4, "test", false); |
| //t.expand acting as a list of values |
| takeSeveralTypes(t2.expand); |
| } |
| |
| static if (is(Specs)) |
| { |
| // This is mostly to make t[n] work. |
| alias expand this; |
| } |
| else |
| { |
| @property |
| ref inout(Tuple!Types) _Tuple_super() inout @trusted |
| { |
| static foreach (i; 0 .. Types.length) // Rely on the field layout |
| { |
| static assert(typeof(return).init.tupleof[i].offsetof == |
| expand[i].offsetof); |
| } |
| return *cast(typeof(return)*) &(field[0]); |
| } |
| // This is mostly to make t[n] work. |
| alias _Tuple_super this; |
| } |
| |
| // backwards compatibility |
| alias field = expand; |
| |
| /** |
| * Constructor taking one value for each field. |
| * |
| * Params: |
| * values = A list of values that are either the same |
| * types as those given by the `Types` field |
| * of this `Tuple`, or can implicitly convert |
| * to those types. They must be in the same |
| * order as they appear in `Types`. |
| */ |
| static if (Types.length > 0) |
| { |
| this(Types values) |
| { |
| field[] = values[]; |
| } |
| } |
| |
| /// |
| static if (Specs.length == 0) @safe unittest |
| { |
| alias ISD = Tuple!(int, string, double); |
| auto tup = ISD(1, "test", 3.2); |
| assert(tup.toString() == `Tuple!(int, string, double)(1, "test", 3.2)`); |
| } |
| |
| /** |
| * Constructor taking a compatible array. |
| * |
| * Params: |
| * values = A compatible static array to build the `Tuple` from. |
| * Array slices are not supported. |
| */ |
| this(U, size_t n)(U[n] values) |
| if (n == Types.length && allSatisfy!(isBuildableFrom!U, Types)) |
| { |
| static foreach (i; 0 .. Types.length) |
| { |
| field[i] = values[i]; |
| } |
| } |
| |
| /// |
| static if (Specs.length == 0) @safe unittest |
| { |
| int[2] ints; |
| Tuple!(int, int) t = ints; |
| } |
| |
| /** |
| * Constructor taking a compatible `Tuple`. Two `Tuple`s are compatible |
| * $(B iff) they are both of the same length, and, for each type `T` on the |
| * left-hand side, the corresponding type `U` on the right-hand side can |
| * implicitly convert to `T`. |
| * |
| * Params: |
| * another = A compatible `Tuple` to build from. Its type must be |
| * compatible with the target `Tuple`'s type. |
| */ |
| this(U)(U another) |
| if (areBuildCompatibleTuples!(typeof(this), U)) |
| { |
| field[] = another.field[]; |
| } |
| |
| /// |
| static if (Specs.length == 0) @safe unittest |
| { |
| alias IntVec = Tuple!(int, int, int); |
| alias DubVec = Tuple!(double, double, double); |
| |
| IntVec iv = tuple(1, 1, 1); |
| |
| //Ok, int can implicitly convert to double |
| DubVec dv = iv; |
| //Error: double cannot implicitly convert to int |
| //IntVec iv2 = dv; |
| } |
| |
| /** |
| * Comparison for equality. Two `Tuple`s are considered equal |
| * $(B iff) they fulfill the following criteria: |
| * |
| * $(UL |
| * $(LI Each `Tuple` is the same length.) |
| * $(LI For each type `T` on the left-hand side and each type |
| * `U` on the right-hand side, values of type `T` can be |
| * compared with values of type `U`.) |
| * $(LI For each value `v1` on the left-hand side and each value |
| * `v2` on the right-hand side, the expression `v1 == v2` is |
| * true.)) |
| * |
| * Params: |
| * rhs = The `Tuple` to compare against. It must meeting the criteria |
| * for comparison between `Tuple`s. |
| * |
| * Returns: |
| * true if both `Tuple`s are equal, otherwise false. |
| */ |
| bool opEquals(R)(R rhs) |
| if (areCompatibleTuples!(typeof(this), R, "==")) |
| { |
| return field[] == rhs.field[]; |
| } |
| |
| /// ditto |
| bool opEquals(R)(R rhs) const |
| if (areCompatibleTuples!(typeof(this), R, "==")) |
| { |
| return field[] == rhs.field[]; |
| } |
| |
| /// ditto |
| bool opEquals(R...)(auto ref R rhs) |
| if (R.length > 1 && areCompatibleTuples!(typeof(this), Tuple!R, "==")) |
| { |
| static foreach (i; 0 .. Types.length) |
| if (field[i] != rhs[i]) |
| return false; |
| |
| return true; |
| } |
| |
| /// |
| static if (Specs.length == 0) @safe unittest |
| { |
| Tuple!(int, string) t1 = tuple(1, "test"); |
| Tuple!(double, string) t2 = tuple(1.0, "test"); |
| //Ok, int can be compared with double and |
| //both have a value of 1 |
| assert(t1 == t2); |
| } |
| |
| /** |
| * Comparison for ordering. |
| * |
| * Params: |
| * rhs = The `Tuple` to compare against. It must meet the criteria |
| * for comparison between `Tuple`s. |
| * |
| * Returns: |
| * For any values `v1` contained by the left-hand side tuple and any |
| * values `v2` contained by the right-hand side: |
| * |
| * 0 if `v1 == v2` for all members or the following value for the |
| * first position were the mentioned criteria is not satisfied: |
| * |
| * $(UL |
| * $(LI NaN, in case one of the operands is a NaN.) |
| * $(LI A negative number if the expression `v1 < v2` is true.) |
| * $(LI A positive number if the expression `v1 > v2` is true.)) |
| */ |
| auto opCmp(R)(R rhs) |
| if (areCompatibleTuples!(typeof(this), R, "<")) |
| { |
| static foreach (i; 0 .. Types.length) |
| { |
| if (field[i] != rhs.field[i]) |
| { |
| import std.math.traits : isNaN; |
| static if (isFloatingPoint!(Types[i])) |
| { |
| if (isNaN(field[i])) |
| return float.nan; |
| } |
| static if (isFloatingPoint!(typeof(rhs.field[i]))) |
| { |
| if (isNaN(rhs.field[i])) |
| return float.nan; |
| } |
| static if (is(typeof(field[i].opCmp(rhs.field[i]))) && |
| isFloatingPoint!(typeof(field[i].opCmp(rhs.field[i])))) |
| { |
| if (isNaN(field[i].opCmp(rhs.field[i]))) |
| return float.nan; |
| } |
| |
| return field[i] < rhs.field[i] ? -1 : 1; |
| } |
| } |
| return 0; |
| } |
| |
| /// ditto |
| auto opCmp(R)(R rhs) const |
| if (areCompatibleTuples!(typeof(this), R, "<")) |
| { |
| static foreach (i; 0 .. Types.length) |
| { |
| if (field[i] != rhs.field[i]) |
| { |
| import std.math.traits : isNaN; |
| static if (isFloatingPoint!(Types[i])) |
| { |
| if (isNaN(field[i])) |
| return float.nan; |
| } |
| static if (isFloatingPoint!(typeof(rhs.field[i]))) |
| { |
| if (isNaN(rhs.field[i])) |
| return float.nan; |
| } |
| static if (is(typeof(field[i].opCmp(rhs.field[i]))) && |
| isFloatingPoint!(typeof(field[i].opCmp(rhs.field[i])))) |
| { |
| if (isNaN(field[i].opCmp(rhs.field[i]))) |
| return float.nan; |
| } |
| |
| return field[i] < rhs.field[i] ? -1 : 1; |
| } |
| } |
| return 0; |
| } |
| |
| /** |
| The first `v1` for which `v1 > v2` is true determines |
| the result. This could lead to unexpected behaviour. |
| */ |
| static if (Specs.length == 0) @safe unittest |
| { |
| auto tup1 = tuple(1, 1, 1); |
| auto tup2 = tuple(1, 100, 100); |
| assert(tup1 < tup2); |
| |
| //Only the first result matters for comparison |
| tup1[0] = 2; |
| assert(tup1 > tup2); |
| } |
| |
| /** |
| Concatenate Tuples. |
| Tuple concatenation is only allowed if all named fields are distinct (no named field of this tuple occurs in `t` |
| and no named field of `t` occurs in this tuple). |
| |
| Params: |
| t = The `Tuple` to concatenate with |
| |
| Returns: A concatenation of this tuple and `t` |
| */ |
| auto opBinary(string op, T)(auto ref T t) |
| if (op == "~" && !(is(T : U[], U) && isTuple!U)) |
| { |
| static if (isTuple!T) |
| { |
| static assert(distinctFieldNames!(_Fields, T._Fields), |
| "Cannot concatenate tuples with duplicate fields: " ~ fieldNames.stringof ~ |
| " - " ~ T.fieldNames.stringof); |
| return Tuple!(_Fields, T._Fields)(expand, t.expand); |
| } |
| else |
| { |
| return Tuple!(_Fields, T)(expand, t); |
| } |
| } |
| |
| /// ditto |
| auto opBinaryRight(string op, T)(auto ref T t) |
| if (op == "~" && !(is(T : U[], U) && isTuple!U)) |
| { |
| static if (isTuple!T) |
| { |
| static assert(distinctFieldNames!(_Fields, T._Fields), |
| "Cannot concatenate tuples with duplicate fields: " ~ T.stringof ~ |
| " - " ~ fieldNames.fieldNames.stringof); |
| return Tuple!(T._Fields, _Fields)(t.expand, expand); |
| } |
| else |
| { |
| return Tuple!(T, _Fields)(t, expand); |
| } |
| } |
| |
| /** |
| * Assignment from another `Tuple`. |
| * |
| * Params: |
| * rhs = The source `Tuple` to assign from. Each element of the |
| * source `Tuple` must be implicitly assignable to each |
| * respective element of the target `Tuple`. |
| */ |
| ref Tuple opAssign(R)(auto ref R rhs) |
| if (areCompatibleTuples!(typeof(this), R, "=")) |
| { |
| import std.algorithm.mutation : swap; |
| |
| static if (is(R : Tuple!Types) && !__traits(isRef, rhs) && isTuple!R) |
| { |
| if (__ctfe) |
| { |
| // Cannot use swap at compile time |
| field[] = rhs.field[]; |
| } |
| else |
| { |
| // Use swap-and-destroy to optimize rvalue assignment |
| swap!(Tuple!Types)(this, rhs); |
| } |
| } |
| else |
| { |
| // Do not swap; opAssign should be called on the fields. |
| field[] = rhs.field[]; |
| } |
| return this; |
| } |
| |
| /** |
| * Renames the elements of a $(LREF Tuple). |
| * |
| * `rename` uses the passed `names` and returns a new |
| * $(LREF Tuple) using these names, with the content |
| * unchanged. |
| * If fewer names are passed than there are members |
| * of the $(LREF Tuple) then those trailing members are unchanged. |
| * An empty string will remove the name for that member. |
| * It is an compile-time error to pass more names than |
| * there are members of the $(LREF Tuple). |
| */ |
| ref rename(names...)() inout return |
| if (names.length == 0 || allSatisfy!(isSomeString, typeof(names))) |
| { |
| import std.algorithm.comparison : equal; |
| // to circumvent https://issues.dlang.org/show_bug.cgi?id=16418 |
| static if (names.length == 0 || equal([names], [fieldNames])) |
| return this; |
| else |
| { |
| enum nT = Types.length; |
| enum nN = names.length; |
| static assert(nN <= nT, "Cannot have more names than tuple members"); |
| alias allNames = AliasSeq!(names, fieldNames[nN .. $]); |
| |
| import std.meta : Alias, aliasSeqOf; |
| |
| template GetItem(size_t idx) |
| { |
| import std.array : empty; |
| static if (idx < nT) |
| alias GetItem = Alias!(Types[idx]); |
| else static if (allNames[idx - nT].empty) |
| alias GetItem = AliasSeq!(); |
| else |
| alias GetItem = Alias!(allNames[idx - nT]); |
| } |
| |
| import std.range : roundRobin, iota; |
| alias NewTupleT = Tuple!(staticMap!(GetItem, aliasSeqOf!( |
| roundRobin(iota(nT), iota(nT, 2*nT))))); |
| return *(() @trusted => cast(NewTupleT*)&this)(); |
| } |
| } |
| |
| /// |
| static if (Specs.length == 0) @safe unittest |
| { |
| auto t0 = tuple(4, "hello"); |
| |
| auto t0Named = t0.rename!("val", "tag"); |
| assert(t0Named.val == 4); |
| assert(t0Named.tag == "hello"); |
| |
| Tuple!(float, "dat", size_t[2], "pos") t1; |
| t1.pos = [2, 1]; |
| auto t1Named = t1.rename!"height"; |
| t1Named.height = 3.4f; |
| assert(t1Named.height == 3.4f); |
| assert(t1Named.pos == [2, 1]); |
| t1Named.rename!"altitude".altitude = 5; |
| assert(t1Named.height == 5); |
| |
| Tuple!(int, "a", int, int, "c") t2; |
| t2 = tuple(3,4,5); |
| auto t2Named = t2.rename!("", "b"); |
| // "a" no longer has a name |
| static assert(!__traits(hasMember, typeof(t2Named), "a")); |
| assert(t2Named[0] == 3); |
| assert(t2Named.b == 4); |
| assert(t2Named.c == 5); |
| |
| // not allowed to specify more names than the tuple has members |
| static assert(!__traits(compiles, t2.rename!("a","b","c","d"))); |
| |
| // use it in a range pipeline |
| import std.range : iota, zip; |
| import std.algorithm.iteration : map, sum; |
| auto res = zip(iota(1, 4), iota(10, 13)) |
| .map!(t => t.rename!("a", "b")) |
| .map!(t => t.a * t.b) |
| .sum; |
| assert(res == 68); |
| |
| const tup = Tuple!(int, "a", int, "b")(2, 3); |
| const renamed = tup.rename!("c", "d"); |
| assert(renamed.c + renamed.d == 5); |
| } |
| |
| /** |
| * Overload of $(LREF _rename) that takes an associative array |
| * `translate` as a template parameter, where the keys are |
| * either the names or indices of the members to be changed |
| * and the new names are the corresponding values. |
| * Every key in `translate` must be the name of a member of the |
| * $(LREF tuple). |
| * The same rules for empty strings apply as for the variadic |
| * template overload of $(LREF _rename). |
| */ |
| ref rename(alias translate)() inout |
| if (is(typeof(translate) : V[K], V, K) && isSomeString!V && |
| (isSomeString!K || is(K : size_t))) |
| { |
| import std.meta : aliasSeqOf; |
| import std.range : ElementType; |
| static if (isSomeString!(ElementType!(typeof(translate.keys)))) |
| { |
| { |
| import std.conv : to; |
| import std.algorithm.iteration : filter; |
| import std.algorithm.searching : canFind; |
| enum notFound = translate.keys |
| .filter!(k => fieldNames.canFind(k) == -1); |
| static assert(notFound.empty, "Cannot find members " |
| ~ notFound.to!string ~ " in type " |
| ~ typeof(this).stringof); |
| } |
| return this.rename!(aliasSeqOf!( |
| { |
| import std.array : empty; |
| auto names = [fieldNames]; |
| foreach (ref n; names) |
| if (!n.empty) |
| if (auto p = n in translate) |
| n = *p; |
| return names; |
| }())); |
| } |
| else |
| { |
| { |
| import std.algorithm.iteration : filter; |
| import std.conv : to; |
| enum invalid = translate.keys. |
| filter!(k => k < 0 || k >= this.length); |
| static assert(invalid.empty, "Indices " ~ invalid.to!string |
| ~ " are out of bounds for tuple with length " |
| ~ this.length.to!string); |
| } |
| return this.rename!(aliasSeqOf!( |
| { |
| auto names = [fieldNames]; |
| foreach (k, v; translate) |
| names[k] = v; |
| return names; |
| }())); |
| } |
| } |
| |
| /// |
| static if (Specs.length == 0) @safe unittest |
| { |
| //replacing names by their current name |
| |
| Tuple!(float, "dat", size_t[2], "pos") t1; |
| t1.pos = [2, 1]; |
| auto t1Named = t1.rename!(["dat": "height"]); |
| t1Named.height = 3.4; |
| assert(t1Named.pos == [2, 1]); |
| t1Named.rename!(["height": "altitude"]).altitude = 5; |
| assert(t1Named.height == 5); |
| |
| Tuple!(int, "a", int, "b") t2; |
| t2 = tuple(3, 4); |
| auto t2Named = t2.rename!(["a": "b", "b": "c"]); |
| assert(t2Named.b == 3); |
| assert(t2Named.c == 4); |
| |
| const t3 = Tuple!(int, "a", int, "b")(3, 4); |
| const t3Named = t3.rename!(["a": "b", "b": "c"]); |
| assert(t3Named.b == 3); |
| assert(t3Named.c == 4); |
| } |
| |
| /// |
| static if (Specs.length == 0) @safe unittest |
| { |
| //replace names by their position |
| |
| Tuple!(float, "dat", size_t[2], "pos") t1; |
| t1.pos = [2, 1]; |
| auto t1Named = t1.rename!([0: "height"]); |
| t1Named.height = 3.4; |
| assert(t1Named.pos == [2, 1]); |
| t1Named.rename!([0: "altitude"]).altitude = 5; |
| assert(t1Named.height == 5); |
| |
| Tuple!(int, "a", int, "b", int, "c") t2; |
| t2 = tuple(3, 4, 5); |
| auto t2Named = t2.rename!([0: "c", 2: "a"]); |
| assert(t2Named.a == 5); |
| assert(t2Named.b == 4); |
| assert(t2Named.c == 3); |
| } |
| |
| static if (Specs.length == 0) @safe unittest |
| { |
| //check that empty translations work fine |
| enum string[string] a0 = null; |
| enum string[int] a1 = null; |
| Tuple!(float, "a", float, "b") t0; |
| |
| auto t1 = t0.rename!a0; |
| |
| t1.a = 3; |
| t1.b = 4; |
| auto t2 = t0.rename!a1; |
| t2.a = 3; |
| t2.b = 4; |
| auto t3 = t0.rename; |
| t3.a = 3; |
| t3.b = 4; |
| } |
| |
| /** |
| * Takes a slice by-reference of this `Tuple`. |
| * |
| * Params: |
| * from = A `size_t` designating the starting position of the slice. |
| * to = A `size_t` designating the ending position (exclusive) of the slice. |
| * |
| * Returns: |
| * A new `Tuple` that is a slice from `[from, to$(RPAREN)` of the original. |
| * It has the same types and values as the range `[from, to$(RPAREN)` in |
| * the original. |
| */ |
| @property |
| ref inout(Tuple!(sliceSpecs!(from, to))) slice(size_t from, size_t to)() inout @trusted |
| if (from <= to && to <= Types.length) |
| { |
| static assert( |
| (typeof(this).alignof % typeof(return).alignof == 0) && |
| (expand[from].offsetof % typeof(return).alignof == 0), |
| "Slicing by reference is impossible because of an alignment mistmatch" ~ |
| " (See https://issues.dlang.org/show_bug.cgi?id=15645)."); |
| |
| return *cast(typeof(return)*) &(field[from]); |
| } |
| |
| /// |
| static if (Specs.length == 0) @safe unittest |
| { |
| Tuple!(int, string, float, double) a; |
| a[1] = "abc"; |
| a[2] = 4.5; |
| auto s = a.slice!(1, 3); |
| static assert(is(typeof(s) == Tuple!(string, float))); |
| assert(s[0] == "abc" && s[1] == 4.5); |
| |
| // https://issues.dlang.org/show_bug.cgi?id=15645 |
| Tuple!(int, short, bool, double) b; |
| static assert(!__traits(compiles, b.slice!(2, 4))); |
| } |
| |
| /** |
| Creates a hash of this `Tuple`. |
| |
| Returns: |
| A `size_t` representing the hash of this `Tuple`. |
| */ |
| size_t toHash() const nothrow @safe |
| { |
| size_t h = 0; |
| static foreach (i, T; Types) |
| {{ |
| static if (__traits(compiles, h = .hashOf(field[i]))) |
| const k = .hashOf(field[i]); |
| else |
| { |
| // Workaround for when .hashOf is not both @safe and nothrow. |
| static if (is(T : shared U, U) && __traits(compiles, (U* a) nothrow @safe => .hashOf(*a)) |
| && !__traits(hasMember, T, "toHash")) |
| // BUG: Improperly casts away `shared`! |
| const k = .hashOf(*(() @trusted => cast(U*) &field[i])()); |
| else |
| // BUG: Improperly casts away `shared`! |
| const k = typeid(T).getHash((() @trusted => cast(const void*) &field[i])()); |
| } |
| static if (i == 0) |
| h = k; |
| else |
| // As in boost::hash_combine |
| // https://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine |
| h ^= k + 0x9e3779b9 + (h << 6) + (h >>> 2); |
| }} |
| return h; |
| } |
| |
| /** |
| * Converts to string. |
| * |
| * Returns: |
| * The string representation of this `Tuple`. |
| */ |
| string toString()() const |
| { |
| import std.array : appender; |
| auto app = appender!string(); |
| this.toString((const(char)[] chunk) => app ~= chunk); |
| return app.data; |
| } |
| |
| import std.format.spec : FormatSpec; |
| |
| /** |
| * Formats `Tuple` with either `%s`, `%(inner%)` or `%(inner%|sep%)`. |
| * |
| * $(TABLE2 Formats supported by Tuple, |
| * $(THEAD Format, Description) |
| * $(TROW $(P `%s`), $(P Format like `Tuple!(types)(elements formatted with %s each)`.)) |
| * $(TROW $(P `%(inner%)`), $(P The format `inner` is applied the expanded `Tuple`$(COMMA) so |
| * it may contain as many formats as the `Tuple` has fields.)) |
| * $(TROW $(P `%(inner%|sep%)`), $(P The format `inner` is one format$(COMMA) that is applied |
| * on all fields of the `Tuple`. The inner format must be compatible to all |
| * of them.))) |
| * |
| * Params: |
| * sink = A `char` accepting delegate |
| * fmt = A $(REF FormatSpec, std,format) |
| */ |
| void toString(DG)(scope DG sink) const |
| { |
| auto f = FormatSpec!char(); |
| toString(sink, f); |
| } |
| |
| /// ditto |
| void toString(DG, Char)(scope DG sink, scope const ref FormatSpec!Char fmt) const |
| { |
| import std.format : format, FormatException; |
| import std.format.write : formattedWrite; |
| import std.range : only; |
| if (fmt.nested) |
| { |
| if (fmt.sep) |
| { |
| foreach (i, Type; Types) |
| { |
| static if (i > 0) |
| { |
| sink(fmt.sep); |
| } |
| // TODO: Change this once formattedWrite() works for shared objects. |
| static if (is(Type == class) && is(Type == shared)) |
| { |
| sink(Type.stringof); |
| } |
| else |
| { |
| formattedWrite(sink, fmt.nested, this.field[i]); |
| } |
| } |
| } |
| else |
| { |
| formattedWrite(sink, fmt.nested, staticMap!(sharedToString, this.expand)); |
| } |
| } |
| else if (fmt.spec == 's') |
| { |
| enum header = Unqual!(typeof(this)).stringof ~ "(", |
| footer = ")", |
| separator = ", "; |
| sink(header); |
| foreach (i, Type; Types) |
| { |
| static if (i > 0) |
| { |
| sink(separator); |
| } |
| // TODO: Change this once format() works for shared objects. |
| static if (is(Type == class) && is(Type == shared)) |
| { |
| sink(Type.stringof); |
| } |
| else |
| { |
| sink(format!("%(%s%)")(only(field[i]))); |
| } |
| } |
| sink(footer); |
| } |
| else |
| { |
| const spec = fmt.spec; |
| throw new FormatException( |
| "Expected '%s' or '%(...%)' or '%(...%|...%)' format specifier for type '" ~ |
| Unqual!(typeof(this)).stringof ~ "', not '%" ~ spec ~ "'."); |
| } |
| } |
| |
| /// |
| static if (Specs.length == 0) @safe unittest |
| { |
| import std.format : format; |
| |
| Tuple!(int, double)[3] tupList = [ tuple(1, 1.0), tuple(2, 4.0), tuple(3, 9.0) ]; |
| |
| // Default format |
| assert(format("%s", tuple("a", 1)) == `Tuple!(string, int)("a", 1)`); |
| |
| // One Format for each individual component |
| assert(format("%(%#x v %.4f w %#x%)", tuple(1, 1.0, 10)) == `0x1 v 1.0000 w 0xa`); |
| assert(format( "%#x v %.4f w %#x" , tuple(1, 1.0, 10).expand) == `0x1 v 1.0000 w 0xa`); |
| |
| // One Format for all components |
| assert(format("%(>%s<%| & %)", tuple("abc", 1, 2.3, [4, 5])) == `>abc< & >1< & >2.3< & >[4, 5]<`); |
| |
| // Array of Tuples |
| assert(format("%(%(f(%d) = %.1f%); %)", tupList) == `f(1) = 1.0; f(2) = 4.0; f(3) = 9.0`); |
| } |
| |
| /// |
| static if (Specs.length == 0) @safe unittest |
| { |
| import std.exception : assertThrown; |
| import std.format : format, FormatException; |
| |
| // Error: %( %) missing. |
| assertThrown!FormatException( |
| format("%d, %f", tuple(1, 2.0)) == `1, 2.0` |
| ); |
| |
| // Error: %( %| %) missing. |
| assertThrown!FormatException( |
| format("%d", tuple(1, 2)) == `1, 2` |
| ); |
| |
| // Error: %d inadequate for double |
| assertThrown!FormatException( |
| format("%(%d%|, %)", tuple(1, 2.0)) == `1, 2.0` |
| ); |
| } |
| } |
| } |
| |
| /// |
| @safe unittest |
| { |
| Tuple!(int, int) point; |
| // assign coordinates |
| point[0] = 5; |
| point[1] = 6; |
| // read coordinates |
| auto x = point[0]; |
| auto y = point[1]; |
| } |
| |
| /** |
| `Tuple` members can be named. It is legal to mix named and unnamed |
| members. The method above is still applicable to all fields. |
| */ |
| @safe unittest |
| { |
| alias Entry = Tuple!(int, "index", string, "value"); |
| Entry e; |
| e.index = 4; |
| e.value = "Hello"; |
| assert(e[1] == "Hello"); |
| assert(e[0] == 4); |
| } |
| |
| /** |
| A `Tuple` with named fields is a distinct type from a `Tuple` with unnamed |
| fields, i.e. each naming imparts a separate type for the `Tuple`. Two |
| `Tuple`s differing in naming only are still distinct, even though they |
| might have the same structure. |
| */ |
| @safe unittest |
| { |
| Tuple!(int, "x", int, "y") point1; |
| Tuple!(int, int) point2; |
| assert(!is(typeof(point1) == typeof(point2))); |
| } |
| |
| /// Use tuples as ranges |
| @safe unittest |
| { |
| import std.algorithm.iteration : sum; |
| import std.range : only; |
| auto t = tuple(1, 2); |
| assert(t.expand.only.sum == 3); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=4582 |
| @safe unittest |
| { |
| static assert(!__traits(compiles, Tuple!(string, "id", int, "id"))); |
| static assert(!__traits(compiles, Tuple!(string, "str", int, "i", string, "str", float))); |
| } |
| |
| /// Concatenate tuples |
| @safe unittest |
| { |
| import std.meta : AliasSeq; |
| auto t = tuple(1, "2") ~ tuple(ushort(42), true); |
| static assert(is(t.Types == AliasSeq!(int, string, ushort, bool))); |
| assert(t[1] == "2"); |
| assert(t[2] == 42); |
| assert(t[3] == true); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=14637 |
| // tuple concat |
| @safe unittest |
| { |
| auto t = tuple!"foo"(1.0) ~ tuple!"bar"("3"); |
| static assert(is(t.Types == AliasSeq!(double, string))); |
| static assert(t.fieldNames == tuple("foo", "bar")); |
| assert(t.foo == 1.0); |
| assert(t.bar == "3"); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=18824 |
| // tuple concat |
| @safe unittest |
| { |
| alias Type = Tuple!(int, string); |
| Type[] arr; |
| auto t = tuple(2, "s"); |
| // Test opBinaryRight |
| arr = arr ~ t; |
| // Test opBinary |
| arr = t ~ arr; |
| static assert(is(typeof(arr) == Type[])); |
| immutable Type[] b; |
| auto c = b ~ t; |
| static assert(is(typeof(c) == immutable(Type)[])); |
| } |
| |
| // tuple concat |
| @safe unittest |
| { |
| auto t = tuple!"foo"(1.0) ~ "3"; |
| static assert(is(t.Types == AliasSeq!(double, string))); |
| assert(t.foo == 1.0); |
| assert(t[1]== "3"); |
| } |
| |
| // tuple concat |
| @safe unittest |
| { |
| auto t = "2" ~ tuple!"foo"(1.0); |
| static assert(is(t.Types == AliasSeq!(string, double))); |
| assert(t.foo == 1.0); |
| assert(t[0]== "2"); |
| } |
| |
| // tuple concat |
| @safe unittest |
| { |
| auto t = "2" ~ tuple!"foo"(1.0) ~ tuple(42, 3.0f) ~ real(1) ~ "a"; |
| static assert(is(t.Types == AliasSeq!(string, double, int, float, real, string))); |
| assert(t.foo == 1.0); |
| assert(t[0] == "2"); |
| assert(t[1] == 1.0); |
| assert(t[2] == 42); |
| assert(t[3] == 3.0f); |
| assert(t[4] == 1.0); |
| assert(t[5] == "a"); |
| } |
| |
| // ensure that concatenation of tuples with non-distinct fields is forbidden |
| @safe unittest |
| { |
| static assert(!__traits(compiles, |
| tuple!("a")(0) ~ tuple!("a")("1"))); |
| static assert(!__traits(compiles, |
| tuple!("a", "b")(0, 1) ~ tuple!("b", "a")("3", 1))); |
| static assert(!__traits(compiles, |
| tuple!("a")(0) ~ tuple!("b", "a")("3", 1))); |
| static assert(!__traits(compiles, |
| tuple!("a1", "a")(1.0, 0) ~ tuple!("a2", "a")("3", 0))); |
| } |
| |
| // Ensure that Tuple comparison with non-const opEquals works |
| @safe unittest |
| { |
| static struct Bad |
| { |
| int a; |
| |
| bool opEquals(Bad b) |
| { |
| return a == b.a; |
| } |
| } |
| |
| auto t = Tuple!(int, Bad, string)(1, Bad(1), "asdf"); |
| |
| //Error: mutable method Bad.opEquals is not callable using a const object |
| assert(t == AliasSeq!(1, Bad(1), "asdf")); |
| } |
| |
| // Ensure Tuple.toHash works |
| @safe unittest |
| { |
| Tuple!(int, int) point; |
| assert(point.toHash == typeof(point).init.toHash); |
| assert(tuple(1, 2) != point); |
| assert(tuple(1, 2) == tuple(1, 2)); |
| point[0] = 1; |
| assert(tuple(1, 2) != point); |
| point[1] = 2; |
| assert(tuple(1, 2) == point); |
| } |
| |
| @safe @betterC unittest |
| { |
| auto t = tuple(1, 2); |
| assert(t == tuple(1, 2)); |
| auto t3 = tuple(1, 'd'); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=20850 |
| // Assignment to enum tuple |
| @safe unittest |
| { |
| enum T : Tuple!(int*) { a = T(null) } |
| T t; |
| t = T.a; |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=13663 |
| @safe unittest |
| { |
| auto t = tuple(real.nan); |
| assert(!(t > t)); |
| assert(!(t < t)); |
| assert(!(t == t)); |
| } |
| |
| @safe unittest |
| { |
| struct S |
| { |
| float opCmp(S s) { return float.nan; } |
| bool opEquals(S s) { return false; } |
| } |
| |
| auto t = tuple(S()); |
| assert(!(t > t)); |
| assert(!(t < t)); |
| assert(!(t == t)); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=8015 |
| @safe unittest |
| { |
| struct MyStruct |
| { |
| string str; |
| @property string toStr() |
| { |
| return str; |
| } |
| alias toStr this; |
| } |
| |
| Tuple!(MyStruct) t; |
| } |
| |
| /** |
| Creates a copy of a $(LREF Tuple) with its fields in _reverse order. |
| |
| Params: |
| t = The `Tuple` to copy. |
| |
| Returns: |
| A new `Tuple`. |
| */ |
| auto reverse(T)(T t) |
| if (isTuple!T) |
| { |
| import std.meta : Reverse; |
| // @@@BUG@@@ Cannot be an internal function due to forward reference issues. |
| |
| // @@@BUG@@@ 9929 Need 'this' when calling template with expanded tuple |
| // return tuple(Reverse!(t.expand)); |
| |
| ReverseTupleType!T result; |
| auto tup = t.expand; |
| result.expand = Reverse!tup; |
| return result; |
| } |
| |
| /// |
| @safe unittest |
| { |
| auto tup = tuple(1, "2"); |
| assert(tup.reverse == tuple("2", 1)); |
| } |
| |
| /* Get a Tuple type with the reverse specification of Tuple T. */ |
| private template ReverseTupleType(T) |
| if (isTuple!T) |
| { |
| static if (is(T : Tuple!A, A...)) |
| alias ReverseTupleType = Tuple!(ReverseTupleSpecs!A); |
| } |
| |
| /* Reverse the Specs of a Tuple. */ |
| private template ReverseTupleSpecs(T...) |
| { |
| static if (T.length > 1) |
| { |
| static if (is(typeof(T[$-1]) : string)) |
| { |
| alias ReverseTupleSpecs = AliasSeq!(T[$-2], T[$-1], ReverseTupleSpecs!(T[0 .. $-2])); |
| } |
| else |
| { |
| alias ReverseTupleSpecs = AliasSeq!(T[$-1], ReverseTupleSpecs!(T[0 .. $-1])); |
| } |
| } |
| else |
| { |
| alias ReverseTupleSpecs = T; |
| } |
| } |
| |
| // ensure that internal Tuple unittests are compiled |
| @safe unittest |
| { |
| Tuple!() t; |
| } |
| |
| @safe unittest |
| { |
| import std.conv; |
| { |
| Tuple!(int, "a", int, "b") nosh; |
| static assert(nosh.length == 2); |
| nosh.a = 5; |
| nosh.b = 6; |
| assert(nosh.a == 5); |
| assert(nosh.b == 6); |
| } |
| { |
| Tuple!(short, double) b; |
| static assert(b.length == 2); |
| b[1] = 5; |
| auto a = Tuple!(int, real)(b); |
| assert(a[0] == 0 && a[1] == 5); |
| a = Tuple!(int, real)(1, 2); |
| assert(a[0] == 1 && a[1] == 2); |
| auto c = Tuple!(int, "a", double, "b")(a); |
| assert(c[0] == 1 && c[1] == 2); |
| } |
| { |
| Tuple!(int, real) nosh; |
| nosh[0] = 5; |
| nosh[1] = 0; |
| assert(nosh[0] == 5 && nosh[1] == 0); |
| assert(nosh.to!string == "Tuple!(int, real)(5, 0)", nosh.to!string); |
| Tuple!(int, int) yessh; |
| nosh = yessh; |
| } |
| { |
| class A {} |
| Tuple!(int, shared A) nosh; |
| nosh[0] = 5; |
| assert(nosh[0] == 5 && nosh[1] is null); |
| assert(nosh.to!string == "Tuple!(int, shared(A))(5, shared(A))"); |
| } |
| { |
| Tuple!(int, string) t; |
| t[0] = 10; |
| t[1] = "str"; |
| assert(t[0] == 10 && t[1] == "str"); |
| assert(t.to!string == `Tuple!(int, string)(10, "str")`, t.to!string); |
| } |
| { |
| Tuple!(int, "a", double, "b") x; |
| static assert(x.a.offsetof == x[0].offsetof); |
| static assert(x.b.offsetof == x[1].offsetof); |
| x.b = 4.5; |
| x.a = 5; |
| assert(x[0] == 5 && x[1] == 4.5); |
| assert(x.a == 5 && x.b == 4.5); |
| } |
| // indexing |
| { |
| Tuple!(int, real) t; |
| static assert(is(typeof(t[0]) == int)); |
| static assert(is(typeof(t[1]) == real)); |
| int* p0 = &t[0]; |
| real* p1 = &t[1]; |
| t[0] = 10; |
| t[1] = -200.0L; |
| assert(*p0 == t[0]); |
| assert(*p1 == t[1]); |
| } |
| // slicing |
| { |
| Tuple!(int, "x", real, "y", double, "z", string) t; |
| t[0] = 10; |
| t[1] = 11; |
| t[2] = 12; |
| t[3] = "abc"; |
| auto a = t.slice!(0, 3); |
| assert(a.length == 3); |
| assert(a.x == t.x); |
| assert(a.y == t.y); |
| assert(a.z == t.z); |
| auto b = t.slice!(2, 4); |
| assert(b.length == 2); |
| assert(b.z == t.z); |
| assert(b[1] == t[3]); |
| } |
| // nesting |
| { |
| Tuple!(Tuple!(int, real), Tuple!(string, "s")) t; |
| static assert(is(typeof(t[0]) == Tuple!(int, real))); |
| static assert(is(typeof(t[1]) == Tuple!(string, "s"))); |
| static assert(is(typeof(t[0][0]) == int)); |
| static assert(is(typeof(t[0][1]) == real)); |
| static assert(is(typeof(t[1].s) == string)); |
| t[0] = tuple(10, 20.0L); |
| t[1].s = "abc"; |
| assert(t[0][0] == 10); |
| assert(t[0][1] == 20.0L); |
| assert(t[1].s == "abc"); |
| } |
| // non-POD |
| { |
| static struct S |
| { |
| int count; |
| this(this) { ++count; } |
| ~this() { --count; } |
| void opAssign(S rhs) { count = rhs.count; } |
| } |
| Tuple!(S, S) ss; |
| Tuple!(S, S) ssCopy = ss; |
| assert(ssCopy[0].count == 1); |
| assert(ssCopy[1].count == 1); |
| ssCopy[1] = ssCopy[0]; |
| assert(ssCopy[1].count == 2); |
| } |
| // https://issues.dlang.org/show_bug.cgi?id=2800 |
| { |
| static struct R |
| { |
| Tuple!(int, int) _front; |
| @property ref Tuple!(int, int) front() return { return _front; } |
| @property bool empty() { return _front[0] >= 10; } |
| void popFront() { ++_front[0]; } |
| } |
| foreach (a; R()) |
| { |
| static assert(is(typeof(a) == Tuple!(int, int))); |
| assert(0 <= a[0] && a[0] < 10); |
| assert(a[1] == 0); |
| } |
| } |
| // Construction with compatible elements |
| { |
| auto t1 = Tuple!(int, double)(1, 1); |
| |
| // https://issues.dlang.org/show_bug.cgi?id=8702 |
| auto t8702a = tuple(tuple(1)); |
| auto t8702b = Tuple!(Tuple!(int))(Tuple!(int)(1)); |
| } |
| // Construction with compatible tuple |
| { |
| Tuple!(int, int) x; |
| x[0] = 10; |
| x[1] = 20; |
| Tuple!(int, "a", double, "b") y = x; |
| assert(y.a == 10); |
| assert(y.b == 20); |
| // incompatible |
| static assert(!__traits(compiles, Tuple!(int, int)(y))); |
| } |
| // https://issues.dlang.org/show_bug.cgi?id=6275 |
| { |
| const int x = 1; |
| auto t1 = tuple(x); |
| alias T = Tuple!(const(int)); |
| auto t2 = T(1); |
| } |
| // https://issues.dlang.org/show_bug.cgi?id=9431 |
| { |
| alias T = Tuple!(int[1][]); |
| auto t = T([[10]]); |
| } |
| // https://issues.dlang.org/show_bug.cgi?id=7666 |
| { |
| auto tup = tuple(1, "2"); |
| assert(tup.reverse == tuple("2", 1)); |
| } |
| { |
| Tuple!(int, "x", string, "y") tup = tuple(1, "2"); |
| auto rev = tup.reverse; |
| assert(rev == tuple("2", 1)); |
| assert(rev.x == 1 && rev.y == "2"); |
| } |
| { |
| Tuple!(wchar, dchar, int, "x", string, "y", char, byte, float) tup; |
| tup = tuple('a', 'b', 3, "4", 'c', cast(byte) 0x0D, 0.00); |
| auto rev = tup.reverse; |
| assert(rev == tuple(0.00, cast(byte) 0x0D, 'c', "4", 3, 'b', 'a')); |
| assert(rev.x == 3 && rev.y == "4"); |
| } |
| } |
| @safe unittest |
| { |
| // opEquals |
| { |
| struct Equ1 { bool opEquals(Equ1) { return true; } } |
| auto tm1 = tuple(Equ1.init); |
| const tc1 = tuple(Equ1.init); |
| static assert( is(typeof(tm1 == tm1))); |
| static assert(!is(typeof(tm1 == tc1))); |
| static assert(!is(typeof(tc1 == tm1))); |
| static assert(!is(typeof(tc1 == tc1))); |
| |
| struct Equ2 { bool opEquals(const Equ2) const { return true; } } |
| auto tm2 = tuple(Equ2.init); |
| const tc2 = tuple(Equ2.init); |
| static assert( is(typeof(tm2 == tm2))); |
| static assert( is(typeof(tm2 == tc2))); |
| static assert( is(typeof(tc2 == tm2))); |
| static assert( is(typeof(tc2 == tc2))); |
| |
| // https://issues.dlang.org/show_bug.cgi?id=8686 |
| struct Equ3 { bool opEquals(T)(T) { return true; } } |
| auto tm3 = tuple(Equ3.init); |
| const tc3 = tuple(Equ3.init); |
| static assert( is(typeof(tm3 == tm3))); |
| static assert( is(typeof(tm3 == tc3))); |
| static assert(!is(typeof(tc3 == tm3))); |
| static assert(!is(typeof(tc3 == tc3))); |
| |
| struct Equ4 { bool opEquals(T)(T) const { return true; } } |
| auto tm4 = tuple(Equ4.init); |
| const tc4 = tuple(Equ4.init); |
| static assert( is(typeof(tm4 == tm4))); |
| static assert( is(typeof(tm4 == tc4))); |
| static assert( is(typeof(tc4 == tm4))); |
| static assert( is(typeof(tc4 == tc4))); |
| } |
| // opCmp |
| { |
| struct Cmp1 { int opCmp(Cmp1) { return 0; } } |
| auto tm1 = tuple(Cmp1.init); |
| const tc1 = tuple(Cmp1.init); |
| static assert( is(typeof(tm1 < tm1))); |
| static assert(!is(typeof(tm1 < tc1))); |
| static assert(!is(typeof(tc1 < tm1))); |
| static assert(!is(typeof(tc1 < tc1))); |
| |
| struct Cmp2 { int opCmp(const Cmp2) const { return 0; } } |
| auto tm2 = tuple(Cmp2.init); |
| const tc2 = tuple(Cmp2.init); |
| static assert( is(typeof(tm2 < tm2))); |
| static assert( is(typeof(tm2 < tc2))); |
| static assert( is(typeof(tc2 < tm2))); |
| static assert( is(typeof(tc2 < tc2))); |
| |
| struct Cmp3 { int opCmp(T)(T) { return 0; } } |
| auto tm3 = tuple(Cmp3.init); |
| const tc3 = tuple(Cmp3.init); |
| static assert( is(typeof(tm3 < tm3))); |
| static assert( is(typeof(tm3 < tc3))); |
| static assert(!is(typeof(tc3 < tm3))); |
| static assert(!is(typeof(tc3 < tc3))); |
| |
| struct Cmp4 { int opCmp(T)(T) const { return 0; } } |
| auto tm4 = tuple(Cmp4.init); |
| const tc4 = tuple(Cmp4.init); |
| static assert( is(typeof(tm4 < tm4))); |
| static assert( is(typeof(tm4 < tc4))); |
| static assert( is(typeof(tc4 < tm4))); |
| static assert( is(typeof(tc4 < tc4))); |
| } |
| // https://issues.dlang.org/show_bug.cgi?id=14890 |
| static void test14890(inout int[] dummy) |
| { |
| alias V = Tuple!(int, int); |
| |
| V mv; |
| const V cv; |
| immutable V iv; |
| inout V wv; // OK <- NG |
| inout const V wcv; // OK <- NG |
| |
| static foreach (v1; AliasSeq!(mv, cv, iv, wv, wcv)) |
| static foreach (v2; AliasSeq!(mv, cv, iv, wv, wcv)) |
| { |
| assert(!(v1 < v2)); |
| } |
| } |
| { |
| int[2] ints = [ 1, 2 ]; |
| Tuple!(int, int) t = ints; |
| assert(t[0] == 1 && t[1] == 2); |
| Tuple!(long, uint) t2 = ints; |
| assert(t2[0] == 1 && t2[1] == 2); |
| } |
| } |
| @safe unittest |
| { |
| auto t1 = Tuple!(int, "x", string, "y")(1, "a"); |
| assert(t1.x == 1); |
| assert(t1.y == "a"); |
| void foo(Tuple!(int, string) t2) {} |
| foo(t1); |
| |
| Tuple!(int, int)[] arr; |
| arr ~= tuple(10, 20); // OK |
| arr ~= Tuple!(int, "x", int, "y")(10, 20); // NG -> OK |
| |
| static assert(is(typeof(Tuple!(int, "x", string, "y").tupleof) == |
| typeof(Tuple!(int, string ).tupleof))); |
| } |
| @safe unittest |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=10686 |
| immutable Tuple!(int) t1; |
| auto r1 = t1[0]; // OK |
| immutable Tuple!(int, "x") t2; |
| auto r2 = t2[0]; // error |
| } |
| @safe unittest |
| { |
| import std.exception : assertCTFEable; |
| |
| // https://issues.dlang.org/show_bug.cgi?id=10218 |
| assertCTFEable!( |
| { |
| auto t = tuple(1); |
| t = tuple(2); // assignment |
| }); |
| } |
| @safe unittest |
| { |
| class Foo{} |
| Tuple!(immutable(Foo)[]) a; |
| } |
| |
| @safe unittest |
| { |
| //Test non-assignable |
| static struct S |
| { |
| int* p; |
| } |
| alias IS = immutable S; |
| static assert(!isAssignable!IS); |
| |
| auto s = IS.init; |
| |
| alias TIS = Tuple!IS; |
| TIS a = tuple(s); |
| TIS b = a; |
| |
| alias TISIS = Tuple!(IS, IS); |
| TISIS d = tuple(s, s); |
| IS[2] ss; |
| TISIS e = TISIS(ss); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=9819 |
| @safe unittest |
| { |
| alias T = Tuple!(int, "x", double, "foo"); |
| static assert(T.fieldNames[0] == "x"); |
| static assert(T.fieldNames[1] == "foo"); |
| |
| alias Fields = Tuple!(int, "id", string, float); |
| static assert(Fields.fieldNames == AliasSeq!("id", "", "")); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=13837 |
| @safe unittest |
| { |
| // New behaviour, named arguments. |
| static assert(is( |
| typeof(tuple!("x")(1)) == Tuple!(int, "x"))); |
| static assert(is( |
| typeof(tuple!("x")(1.0)) == Tuple!(double, "x"))); |
| static assert(is( |
| typeof(tuple!("x")("foo")) == Tuple!(string, "x"))); |
| static assert(is( |
| typeof(tuple!("x", "y")(1, 2.0)) == Tuple!(int, "x", double, "y"))); |
| |
| auto a = tuple!("a", "b", "c")("1", 2, 3.0f); |
| static assert(is(typeof(a.a) == string)); |
| static assert(is(typeof(a.b) == int)); |
| static assert(is(typeof(a.c) == float)); |
| |
| // Old behaviour, but with explicit type parameters. |
| static assert(is( |
| typeof(tuple!(int, double)(1, 2.0)) == Tuple!(int, double))); |
| static assert(is( |
| typeof(tuple!(const int)(1)) == Tuple!(const int))); |
| static assert(is( |
| typeof(tuple()) == Tuple!())); |
| |
| // Nonsensical behaviour |
| static assert(!__traits(compiles, tuple!(1)(2))); |
| static assert(!__traits(compiles, tuple!("x")(1, 2))); |
| static assert(!__traits(compiles, tuple!("x", "y")(1))); |
| static assert(!__traits(compiles, tuple!("x")())); |
| static assert(!__traits(compiles, tuple!("x", int)(2))); |
| } |
| |
| @safe unittest |
| { |
| class C { override size_t toHash() const nothrow @safe { return 0; } } |
| Tuple!(Rebindable!(const C)) a; |
| Tuple!(const C) b; |
| a = b; |
| } |
| |
| @nogc @safe unittest |
| { |
| alias T = Tuple!(string, "s"); |
| T x; |
| x = T.init; |
| } |
| |
| @safe unittest |
| { |
| import std.format : format, FormatException; |
| import std.exception : assertThrown; |
| |
| //enum tupStr = tuple(1, 1.0).toString; // toString is *impure*. |
| //static assert(tupStr == `Tuple!(int, double)(1, 1)`); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=17803, parte uno |
| @safe unittest |
| { |
| auto a = tuple(3, "foo"); |
| assert(__traits(compiles, { a = (a = a); })); |
| } |
| // Ditto |
| @safe unittest |
| { |
| Tuple!(int[]) a, b, c; |
| a = tuple([0, 1, 2]); |
| c = b = a; |
| assert(a[0].length == b[0].length && b[0].length == c[0].length); |
| assert(a[0].ptr == b[0].ptr && b[0].ptr == c[0].ptr); |
| } |
| |
| /** |
| Constructs a $(LREF Tuple) object instantiated and initialized according to |
| the given arguments. |
| |
| Params: |
| Names = An optional list of strings naming each successive field of the `Tuple` |
| or a list of types that the elements are being casted to. |
| For a list of names, |
| each name matches up with the corresponding field given by `Args`. |
| A name does not have to be provided for every field, but as |
| the names must proceed in order, it is not possible to skip |
| one field and name the next after it. |
| For a list of types, |
| there must be exactly as many types as parameters. |
| */ |
| template tuple(Names...) |
| { |
| /** |
| Params: |
| args = Values to initialize the `Tuple` with. The `Tuple`'s type will |
| be inferred from the types of the values given. |
| |
| Returns: |
| A new `Tuple` with its type inferred from the arguments given. |
| */ |
| auto tuple(Args...)(Args args) |
| { |
| static if (Names.length == 0) |
| { |
| // No specified names, just infer types from Args... |
| return Tuple!Args(args); |
| } |
| else static if (!is(typeof(Names[0]) : string)) |
| { |
| // Names[0] isn't a string, must be explicit types. |
| return Tuple!Names(args); |
| } |
| else |
| { |
| // Names[0] is a string, so must be specifying names. |
| static assert(Names.length == Args.length, |
| "Insufficient number of names given."); |
| |
| // Interleave(a, b).and(c, d) == (a, c, b, d) |
| // This is to get the interleaving of types and names for Tuple |
| // e.g. Tuple!(int, "x", string, "y") |
| template Interleave(A...) |
| { |
| template and(B...) if (B.length == 1) |
| { |
| alias and = AliasSeq!(A[0], B[0]); |
| } |
| |
| template and(B...) if (B.length != 1) |
| { |
| alias and = AliasSeq!(A[0], B[0], |
| Interleave!(A[1..$]).and!(B[1..$])); |
| } |
| } |
| return Tuple!(Interleave!(Args).and!(Names))(args); |
| } |
| } |
| } |
| |
| /// |
| @safe unittest |
| { |
| auto value = tuple(5, 6.7, "hello"); |
| assert(value[0] == 5); |
| assert(value[1] == 6.7); |
| assert(value[2] == "hello"); |
| |
| // Field names can be provided. |
| auto entry = tuple!("index", "value")(4, "Hello"); |
| assert(entry.index == 4); |
| assert(entry.value == "Hello"); |
| } |
| |
| /** |
| Returns `true` if and only if `T` is an instance of `std.typecons.Tuple`. |
| |
| Params: |
| T = The type to check. |
| |
| Returns: |
| true if `T` is a `Tuple` type, false otherwise. |
| */ |
| enum isTuple(T) = __traits(compiles, |
| { |
| void f(Specs...)(Tuple!Specs tup) {} |
| f(T.init); |
| } ); |
| |
| /// |
| @safe unittest |
| { |
| static assert(isTuple!(Tuple!())); |
| static assert(isTuple!(Tuple!(int))); |
| static assert(isTuple!(Tuple!(int, real, string))); |
| static assert(isTuple!(Tuple!(int, "x", real, "y"))); |
| static assert(isTuple!(Tuple!(int, Tuple!(real), string))); |
| } |
| |
| @safe unittest |
| { |
| static assert(isTuple!(const Tuple!(int))); |
| static assert(isTuple!(immutable Tuple!(int))); |
| |
| static assert(!isTuple!(int)); |
| static assert(!isTuple!(const int)); |
| |
| struct S {} |
| static assert(!isTuple!(S)); |
| } |
| |
| // used by both Rebindable and UnqualRef |
| private mixin template RebindableCommon(T, U, alias This) |
| if (is(T == class) || is(T == interface) || isAssociativeArray!T) |
| { |
| private union |
| { |
| T original; |
| U stripped; |
| } |
| |
| void opAssign(return scope T another) pure nothrow @nogc |
| { |
| // If `T` defines `opCast` we must infer the safety |
| static if (hasMember!(T, "opCast")) |
| { |
| // This will allow the compiler to infer the safety of `T.opCast!U` |
| // without generating any runtime cost |
| if (false) { stripped = cast(U) another; } |
| } |
| () @trusted { stripped = cast(U) another; }(); |
| } |
| |
| void opAssign(typeof(this) another) @trusted pure nothrow @nogc |
| { |
| stripped = another.stripped; |
| } |
| |
| static if (is(T == const U) && is(T == const shared U)) |
| { |
| // safely assign immutable to const / const shared |
| void opAssign(This!(immutable U) another) @trusted pure nothrow @nogc |
| { |
| stripped = another.stripped; |
| } |
| } |
| |
| this(T initializer) pure nothrow @nogc |
| { |
| // Infer safety from opAssign |
| opAssign(initializer); |
| } |
| |
| @property inout(T) get() @trusted pure nothrow @nogc return scope inout |
| { |
| return original; |
| } |
| |
| bool opEquals()(auto ref const(typeof(this)) rhs) const |
| { |
| // Must forward explicitly because 'stripped' is part of a union. |
| // The necessary 'toHash' is forwarded to the class via alias this. |
| return stripped == rhs.stripped; |
| } |
| |
| bool opEquals(const(U) rhs) const |
| { |
| return stripped == rhs; |
| } |
| |
| alias get this; |
| } |
| |
| /** |
| `Rebindable!(T)` is a simple, efficient wrapper that behaves just |
| like an object of type `T`, except that you can reassign it to |
| refer to another object. For completeness, `Rebindable!(T)` aliases |
| itself away to `T` if `T` is a non-const object type. |
| |
| You may want to use `Rebindable` when you want to have mutable |
| storage referring to `const` objects, for example an array of |
| references that must be sorted in place. `Rebindable` does not |
| break the soundness of D's type system and does not incur any of the |
| risks usually associated with `cast`. |
| |
| Params: |
| T = An object, interface, array slice type, or associative array type. |
| */ |
| template Rebindable(T) |
| if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) |
| { |
| static if (is(T == const U, U) || is(T == immutable U, U)) |
| { |
| static if (isDynamicArray!T) |
| { |
| import std.range.primitives : ElementEncodingType; |
| alias Rebindable = const(ElementEncodingType!T)[]; |
| } |
| else |
| { |
| struct Rebindable |
| { |
| mixin RebindableCommon!(T, U, Rebindable); |
| } |
| } |
| } |
| else |
| { |
| alias Rebindable = T; |
| } |
| } |
| |
| ///Regular `const` object references cannot be reassigned. |
| @safe unittest |
| { |
| class Widget { int x; int y() @safe const { return x; } } |
| const a = new Widget; |
| // Fine |
| a.y(); |
| // error! can't modify const a |
| // a.x = 5; |
| // error! can't modify const a |
| // a = new Widget; |
| } |
| |
| /** |
| However, `Rebindable!(Widget)` does allow reassignment, |
| while otherwise behaving exactly like a $(D const Widget). |
| */ |
| @safe unittest |
| { |
| class Widget { int x; int y() const @safe { return x; } } |
| auto a = Rebindable!(const Widget)(new Widget); |
| // Fine |
| a.y(); |
| // error! can't modify const a |
| // a.x = 5; |
| // Fine |
| a = new Widget; |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=16054 |
| @safe unittest |
| { |
| Rebindable!(immutable Object) r; |
| static assert(__traits(compiles, r.get())); |
| static assert(!__traits(compiles, &r.get())); |
| } |
| |
| @safe unittest |
| { |
| class CustomToHash |
| { |
| override size_t toHash() const nothrow @trusted { return 42; } |
| } |
| Rebindable!(immutable(CustomToHash)) a = new immutable CustomToHash(); |
| assert(a.toHash() == 42, "Rebindable!A should offer toHash()" |
| ~ " by forwarding to A.toHash()."); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=18615 |
| // Rebindable!A should use A.opEqualsa |
| @system unittest |
| { |
| class CustomOpEq |
| { |
| int x; |
| override bool opEquals(Object rhsObj) |
| { |
| if (auto rhs = cast(const(CustomOpEq)) rhsObj) |
| return this.x == rhs.x; |
| else |
| return false; |
| } |
| } |
| CustomOpEq a = new CustomOpEq(); |
| CustomOpEq b = new CustomOpEq(); |
| assert(a !is b); |
| assert(a == b, "a.x == b.x should be true (0 == 0)."); |
| |
| Rebindable!(const(CustomOpEq)) ra = a; |
| Rebindable!(const(CustomOpEq)) rb = b; |
| assert(ra !is rb); |
| assert(ra == rb, "Rebindable should use CustomOpEq's opEquals, not 'is'."); |
| assert(ra == b, "Rebindable!(someQualifier(A)) should be comparable" |
| ~ " against const(A) via A.opEquals."); |
| assert(a == rb, "Rebindable!(someQualifier(A)) should be comparable" |
| ~ " against const(A) via A.opEquals."); |
| |
| b.x = 1; |
| assert(a != b); |
| assert(ra != b, "Rebindable!(someQualifier(A)) should be comparable" |
| ~ " against const(A) via A.opEquals."); |
| assert(a != rb, "Rebindable!(someQualifier(A)) should be comparable" |
| ~ " against const(A) via A.opEquals."); |
| |
| Rebindable!(const(Object)) o1 = new Object(); |
| Rebindable!(const(Object)) o2 = new Object(); |
| assert(o1 !is o2); |
| assert(o1 == o1, "When the class doesn't provide its own opEquals," |
| ~ " Rebindable treats 'a == b' as 'a is b' like Object.opEquals."); |
| assert(o1 != o2, "When the class doesn't provide its own opEquals," |
| ~ " Rebindable treats 'a == b' as 'a is b' like Object.opEquals."); |
| assert(o1 != new Object(), "Rebindable!(const(Object)) should be" |
| ~ " comparable against Object itself and use Object.opEquals."); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=18755 |
| @safe unittest |
| { |
| static class Foo |
| { |
| auto opCast(T)() @system immutable pure nothrow |
| { |
| *(cast(uint*) 0xdeadbeef) = 0xcafebabe; |
| return T.init; |
| } |
| } |
| |
| static assert(!__traits(compiles, () @safe { |
| auto r = Rebindable!(immutable Foo)(new Foo); |
| })); |
| static assert(__traits(compiles, () @system { |
| auto r = Rebindable!(immutable Foo)(new Foo); |
| })); |
| } |
| |
| /** |
| Convenience function for creating a `Rebindable` using automatic type |
| inference. |
| |
| Params: |
| obj = A reference to an object, interface, associative array, or an array slice |
| to initialize the `Rebindable` with. |
| |
| Returns: |
| A newly constructed `Rebindable` initialized with the given reference. |
| */ |
| Rebindable!T rebindable(T)(T obj) |
| if (is(T == class) || is(T == interface) || isDynamicArray!T || isAssociativeArray!T) |
| { |
| typeof(return) ret; |
| ret = obj; |
| return ret; |
| } |
| |
| /// |
| @system unittest |
| { |
| class C |
| { |
| int payload; |
| this(int p) { payload = p; } |
| } |
| const c = new C(1); |
| |
| auto c2 = c.rebindable; |
| assert(c2.payload == 1); |
| // passing Rebindable to rebindable |
| c2 = c2.rebindable; |
| |
| c2 = new C(2); |
| assert(c2.payload == 2); |
| |
| const c3 = c2.get; |
| assert(c3.payload == 2); |
| } |
| |
| /** |
| This function simply returns the `Rebindable` object passed in. It's useful |
| in generic programming cases when a given object may be either a regular |
| `class` or a `Rebindable`. |
| |
| Params: |
| obj = An instance of Rebindable!T. |
| |
| Returns: |
| `obj` without any modification. |
| */ |
| Rebindable!T rebindable(T)(Rebindable!T obj) |
| { |
| return obj; |
| } |
| |
| // TODO: remove me once the rebindable overloads have been joined |
| /// |
| @system unittest |
| { |
| class C |
| { |
| int payload; |
| this(int p) { payload = p; } |
| } |
| const c = new C(1); |
| |
| auto c2 = c.rebindable; |
| assert(c2.payload == 1); |
| // passing Rebindable to rebindable |
| c2 = c2.rebindable; |
| assert(c2.payload == 1); |
| } |
| |
| @system unittest |
| { |
| interface CI { int foo() const; } |
| class C : CI { |
| int foo() const { return 42; } |
| @property int bar() const { return 23; } |
| } |
| Rebindable!(C) obj0; |
| static assert(is(typeof(obj0) == C)); |
| |
| Rebindable!(const(C)) obj1; |
| static assert(is(typeof(obj1.get) == const(C)), typeof(obj1.get).stringof); |
| static assert(is(typeof(obj1.stripped) == C)); |
| obj1 = new C; |
| assert(obj1.get !is null); |
| obj1 = new const(C); |
| assert(obj1.get !is null); |
| |
| Rebindable!(immutable(C)) obj2; |
| static assert(is(typeof(obj2.get) == immutable(C))); |
| static assert(is(typeof(obj2.stripped) == C)); |
| obj2 = new immutable(C); |
| assert(obj1.get !is null); |
| |
| // test opDot |
| assert(obj2.foo() == 42); |
| assert(obj2.bar == 23); |
| |
| interface I { final int foo() const { return 42; } } |
| Rebindable!(I) obj3; |
| static assert(is(typeof(obj3) == I)); |
| |
| Rebindable!(const I) obj4; |
| static assert(is(typeof(obj4.get) == const I)); |
| static assert(is(typeof(obj4.stripped) == I)); |
| static assert(is(typeof(obj4.foo()) == int)); |
| obj4 = new class I {}; |
| |
| Rebindable!(immutable C) obj5i; |
| Rebindable!(const C) obj5c; |
| obj5c = obj5c; |
| obj5c = obj5i; |
| obj5i = obj5i; |
| static assert(!__traits(compiles, obj5i = obj5c)); |
| |
| // Test the convenience functions. |
| auto obj5convenience = rebindable(obj5i); |
| assert(obj5convenience is obj5i); |
| |
| auto obj6 = rebindable(new immutable(C)); |
| static assert(is(typeof(obj6) == Rebindable!(immutable C))); |
| assert(obj6.foo() == 42); |
| |
| auto obj7 = rebindable(new C); |
| CI interface1 = obj7; |
| auto interfaceRebind1 = rebindable(interface1); |
| assert(interfaceRebind1.foo() == 42); |
| |
| const interface2 = interface1; |
| auto interfaceRebind2 = rebindable(interface2); |
| assert(interfaceRebind2.foo() == 42); |
| |
| auto arr = [1,2,3,4,5]; |
| const arrConst = arr; |
| assert(rebindable(arr) == arr); |
| assert(rebindable(arrConst) == arr); |
| |
| // https://issues.dlang.org/show_bug.cgi?id=7654 |
| immutable(char[]) s7654; |
| Rebindable!(typeof(s7654)) r7654 = s7654; |
| |
| static foreach (T; AliasSeq!(char, wchar, char, int)) |
| { |
| static assert(is(Rebindable!(immutable(T[])) == immutable(T)[])); |
| static assert(is(Rebindable!(const(T[])) == const(T)[])); |
| static assert(is(Rebindable!(T[]) == T[])); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=12046 |
| static assert(!__traits(compiles, Rebindable!(int[1]))); |
| static assert(!__traits(compiles, Rebindable!(const int[1]))); |
| |
| // Pull request 3341 |
| Rebindable!(immutable int[int]) pr3341 = [123:345]; |
| assert(pr3341[123] == 345); |
| immutable int[int] pr3341_aa = [321:543]; |
| pr3341 = pr3341_aa; |
| assert(pr3341[321] == 543); |
| assert(rebindable(pr3341_aa)[321] == 543); |
| } |
| |
| /** |
| Similar to `Rebindable!(T)` but strips all qualifiers from the reference as |
| opposed to just constness / immutability. Primary intended use case is with |
| shared (having thread-local reference to shared class data) |
| |
| Params: |
| T = A class or interface type. |
| */ |
| template UnqualRef(T) |
| if (is(T == class) || is(T == interface)) |
| { |
| static if (is(T == immutable U, U) |
| || is(T == const shared U, U) |
| || is(T == const U, U) |
| || is(T == shared U, U)) |
| { |
| struct UnqualRef |
| { |
| mixin RebindableCommon!(T, U, UnqualRef); |
| } |
| } |
| else |
| { |
| alias UnqualRef = T; |
| } |
| } |
| |
| /// |
| @system unittest |
| { |
| class Data {} |
| |
| static shared(Data) a; |
| static UnqualRef!(shared Data) b; |
| |
| import core.thread; |
| |
| auto thread = new core.thread.Thread({ |
| a = new shared Data(); |
| b = new shared Data(); |
| }); |
| |
| thread.start(); |
| thread.join(); |
| |
| assert(a !is null); |
| assert(b is null); |
| } |
| |
| @safe unittest |
| { |
| class C { } |
| alias T = UnqualRef!(const shared C); |
| static assert(is(typeof(T.stripped) == C)); |
| } |
| |
| |
| |
| /** |
| Order the provided members to minimize size while preserving alignment. |
| Alignment is not always optimal for 80-bit reals, nor for structs declared |
| as align(1). |
| |
| Params: |
| E = A list of the types to be aligned, representing fields |
| of an aggregate such as a `struct` or `class`. |
| |
| names = The names of the fields that are to be aligned. |
| |
| Returns: |
| A string to be mixed in to an aggregate, such as a `struct` or `class`. |
| */ |
| string alignForSize(E...)(const char[][] names...) |
| { |
| // Sort all of the members by .alignof. |
| // BUG: Alignment is not always optimal for align(1) structs |
| // or 80-bit reals or 64-bit primitives on x86. |
| // TRICK: Use the fact that .alignof is always a power of 2, |
| // and maximum 16 on extant systems. Thus, we can perform |
| // a very limited radix sort. |
| // Contains the members with .alignof = 64,32,16,8,4,2,1 |
| |
| assert(E.length == names.length, |
| "alignForSize: There should be as many member names as the types"); |
| |
| string[7] declaration = ["", "", "", "", "", "", ""]; |
| |
| foreach (i, T; E) |
| { |
| auto a = T.alignof; |
| auto k = a >= 64? 0 : a >= 32? 1 : a >= 16? 2 : a >= 8? 3 : a >= 4? 4 : a >= 2? 5 : 6; |
| declaration[k] ~= T.stringof ~ " " ~ names[i] ~ ";\n"; |
| } |
| |
| auto s = ""; |
| foreach (decl; declaration) |
| s ~= decl; |
| return s; |
| } |
| |
| /// |
| @safe unittest |
| { |
| struct Banner { |
| mixin(alignForSize!(byte[6], double)(["name", "height"])); |
| } |
| } |
| |
| @safe unittest |
| { |
| enum x = alignForSize!(int[], char[3], short, double[5])("x", "y","z", "w"); |
| struct Foo { int x; } |
| enum y = alignForSize!(ubyte, Foo, double)("x", "y", "z"); |
| |
| enum passNormalX = x == "double[5] w;\nint[] x;\nshort z;\nchar[3] y;\n"; |
| enum passNormalY = y == "double z;\nFoo y;\nubyte x;\n"; |
| |
| enum passAbnormalX = x == "int[] x;\ndouble[5] w;\nshort z;\nchar[3] y;\n"; |
| enum passAbnormalY = y == "Foo y;\ndouble z;\nubyte x;\n"; |
| // ^ blame https://issues.dlang.org/show_bug.cgi?id=231 |
| |
| static assert(passNormalX || passAbnormalX && double.alignof <= (int[]).alignof); |
| static assert(passNormalY || passAbnormalY && double.alignof <= int.alignof); |
| } |
| |
| // https://issues.dlang.org/show_bug.cgi?id=12914 |
| @safe unittest |
| { |
| immutable string[] fieldNames = ["x", "y"]; |
| struct S |
| { |
| mixin(alignForSize!(byte, int)(fieldNames)); |
| } |
| } |
| |
| /** |
| Defines a value paired with a distinctive "null" state that denotes |
| the absence of a value. If default constructed, a $(D |
| Nullable!T) object starts in the null state. Assigning it renders it |
| non-null. Calling `nullify` can nullify it again. |
| |
| Practically `Nullable!T` stores a `T` and a `bool`. |
| */ |
| struct Nullable(T) |
| { |
| private union DontCallDestructorT |
| { |
| import std.traits : hasIndirections; |
| static if (hasIndirections!T) |
| T payload; |
| else |
| T payload = void; |
| } |
| |
| private DontCallDestructorT _value = DontCallDestructorT.init; |
| |
| private bool _isNull = true; |
| |
| /** |
| * Constructor initializing `this` with `value`. |
| * |
| * Params: |
| * value = The value to initialize this `Nullable` with. |
| */ |
| this(inout T value) inout |
| { |
| _value.payload = value; |
| _isNull = false; |
| } |
| |
| static if (hasElaborateDestructor!T) |
| { |
| ~this() |
| { |
| if (!_isNull) |
| { |
| destroy(_value.payload); |
| } |
| } |
| } |
| |
| static if (__traits(hasPostblit, T)) |
| { |
| this(this) |
| { |
| if (!_isNull) |
| _value.payload.__xpostblit(); |
| } |
| } |
| else static if (__traits(hasCopyConstructor, T)) |
| { |
| this(ref return scope inout Nullable!T rhs) inout |
| { |
| _isNull = rhs._isNull; |
| if (!_isNull) |
| _value.payload = rhs._value.payload; |
| else |
| _value = DontCallDestructorT.init; |
| } |
| } |
| |
| /** |
| * If they are both null, then they are equal. If one is null and the other |
| * is not, then they are not equal. If they are both non-null, then they are |
| * equal if their values are equal. |
| */ |
| bool opEquals(this This, Rhs)(auto ref Rhs rhs) |
| if (!is(CommonType!(This, Rhs) == void)) |
| { |
| static if (is(This == Rhs)) |
| { |
| if (_isNull) |
| return rhs._isNull; |
| if (rhs._isNull) |
| return false; |
| return _value.payload == rhs._value.payload; |
| } |
| else |
| { |
| alias Common = CommonType!(This, Rhs); |
| return cast(Common) this == cast(Common) rhs; |
| } |
| } |
| |
| /// Ditto |
| bool opEquals(this This, Rhs)(auto ref Rhs rhs) |
| if (is(CommonType!(This, Rhs) == void) && is(typeof(this.get == rhs))) |
| { |
| return _isNull ? false : rhs == _value.payload; |
| } |
| |
| /// |
| @safe unittest |
| { |
| Nullable!int empty; |
| Nullable!int a = 42; |
| Nullable!int b = 42; |
| Nullable!int c = 27; |
| |
| assert(empty == empty); |
| assert(empty == Nullable!int.init); |
| assert(empty != a); |
| assert(empty != b); |
|