From 79df48d23bf432d73549eb3d6ad45f02f174ef8c Mon Sep 17 00:00:00 2001 From: Ehud Tamir Date: Thu, 15 Oct 2026 17:44:39 +0300 Subject: [PATCH] Take no `.init` of the inspected type in traits For a struct or a static array, `T.init` makes the compiler build the type's whole default-initializer literal, one entry per static-array element. The range primitives traits, std.traits (CommonType, isIterable, ForeachType, hasElaborateAssign, the *TypeOf alias-this traits, ...), and constraints in std.algorithm, std.array and std.typecons used it only to form an expression of type `T`. They now use lvalueOf and rvalueOf, so their compile-time memory and time no longer grow with the size of `T`. CommonType keeps `.init` for operands that are not a struct or union, since null and array literals merge differently in `?:` than values of the type do. Answers are unchanged, except for a type that declares a member named `init`: `T.init` named that member, so the traits answered for the member instead of the type. --- changelog/traits_no_init.dd | 16 +++++ std/algorithm/iteration.d | 8 +-- std/algorithm/searching.d | 10 +-- std/array.d | 4 +- std/range/package.d | 12 ++++ std/range/primitives.d | 65 +++++++++++++++--- std/traits.d | 129 +++++++++++++++++++++++++++++------- std/typecons.d | 25 ++++--- 8 files changed, 216 insertions(+), 53 deletions(-) create mode 100644 changelog/traits_no_init.dd diff --git a/changelog/traits_no_init.dd b/changelog/traits_no_init.dd new file mode 100644 index 00000000000..d4fc220a242 --- /dev/null +++ b/changelog/traits_no_init.dd @@ -0,0 +1,16 @@ +Range traits and `std.traits` no longer build the `.init` of the type they inspect + +For a struct or a static array, `T.init` makes the compiler build the type's whole +default-initializer literal, with one entry per static-array element. Traits such as +$(REF isInputRange, std, range, primitives), $(REF ElementType, std, range, primitives), +$(REF CommonType, std, traits), $(REF isIterable, std, traits) and +$(REF hasElaborateAssign, std, traits), and the constraints of +$(REF map, std, algorithm, iteration) and $(REF minElement, std, algorithm, searching), +used `T.init` only to form an expression of type `T`. They now use +$(REF lvalueOf, std, traits) and $(REF rvalueOf, std, traits), so their compile-time +memory and time no longer grow with the size of `T`. + +Their answers are unchanged, except for a type that declares a member named `init`. There, +`T.init` named that member, so the traits answered for the member; they now answer for the +type. For example, a range type with a member function `init` is now an input range when its +primitives make it one. diff --git a/std/algorithm/iteration.d b/std/algorithm/iteration.d index 41ce48ec1ca..888bd16a1f7 100644 --- a/std/algorithm/iteration.d +++ b/std/algorithm/iteration.d @@ -658,7 +658,7 @@ if (fun.length >= 1) // this validation loop can be moved into a template. foreach (f; _funs) { - static assert(!is(typeof(f(RE.init)) == void), + static assert(!is(typeof(f(rvalueOf!RE)) == void), "Mapping function(s) must not return void. " ~ " Consider using `each` instead of `map!(" ~ _funs.stringof ~ ")`"); } @@ -771,7 +771,7 @@ private struct MapResult(alias fun, Range) static if (isRandomAccessRange!R) { - static if (is(typeof(Range.init[ulong.max]))) + static if (is(typeof(lvalueOf!Range[ulong.max]))) private alias opIndex_t = ulong; else private alias opIndex_t = uint; @@ -7123,10 +7123,10 @@ if (isInputRange!R && Substs.length >= 2 && !is(CommonType!(Substs) == void)) private { - typeof(R.init.drop(0)) rest; + typeof(rvalueOf!R.drop(0)) rest; Ins needles; - typeof(R.init.take(0)) skip; // skip before next hit + typeof(rvalueOf!R.take(0)) skip; // skip before next hit alias Hit = size_t; // 0 iff no hit, otherwise hit in needles[index-1] alias E = Tuple!(typeof(skip), Hit); Hit hitNr; // hit number: 0 means no hit, otherwise index+1 to needles that matched diff --git a/std/algorithm/searching.d b/std/algorithm/searching.d index 4a25cb2e0ba..94bd58066f2 100644 --- a/std/algorithm/searching.d +++ b/std/algorithm/searching.d @@ -1445,7 +1445,7 @@ Returns: */ private auto extremum(alias map, alias selector = "a < b", Range)(Range r) if (isInputRange!Range && !isInfinite!Range && - is(typeof(unaryFun!map(ElementType!(Range).init)))) + is(typeof(unaryFun!map(rvalueOf!(ElementType!Range))))) in { assert(!r.empty, "r is an empty range"); @@ -1465,7 +1465,7 @@ private auto extremum(alias map, alias selector = "a < b", Range, (Range r, RangeElementType seedElement) if (isInputRange!Range && !isInfinite!Range && !is(CommonType!(ElementType!Range, RangeElementType) == void) && - is(typeof(unaryFun!map(ElementType!(Range).init)))) + is(typeof(unaryFun!map(rvalueOf!(ElementType!Range))))) { import std.typecons : Rebindable2; @@ -1504,7 +1504,7 @@ if (isInputRange!Range && !isInfinite!Range && } else { - alias MapType = Unqual!(typeof(mapFun(CommonElement.init))); + alias MapType = Unqual!(typeof(mapFun(rvalueOf!CommonElement))); MapType extremeElementMapped = mapFun(extremeElement.get); // direct access via a random access range is faster @@ -1539,7 +1539,7 @@ if (isInputRange!Range && !isInfinite!Range && private auto extremum(alias selector = "a < b", Range)(Range r) if (isInputRange!Range && !isInfinite!Range && - !is(typeof(unaryFun!selector(ElementType!(Range).init)))) + !is(typeof(unaryFun!selector(rvalueOf!(ElementType!Range))))) { return extremum!(a => a, selector)(r); } @@ -1550,7 +1550,7 @@ private auto extremum(alias selector = "a < b", Range, (Range r, RangeElementType seedElement) if (isInputRange!Range && !isInfinite!Range && !is(CommonType!(ElementType!Range, RangeElementType) == void) && - !is(typeof(unaryFun!selector(ElementType!(Range).init)))) + !is(typeof(unaryFun!selector(rvalueOf!(ElementType!Range))))) { return extremum!(a => a, selector)(r, seedElement); } diff --git a/std/array.d b/std/array.d index 722495ee118..730102812ab 100644 --- a/std/array.d +++ b/std/array.d @@ -3756,13 +3756,13 @@ if (isDynamicArray!A) enum bool canPutConstRange = isInputRange!(Unqual!Range) && !isInputRange!Range && - is(typeof(Appender.init.put(Range.init.front))); + is(typeof(Appender.init.put(rvalueOf!Range.front))); } private template canPutRange(Range) { enum bool canPutRange = isInputRange!Range && - is(typeof(Appender.init.put(Range.init.front))); + is(typeof(Appender.init.put(rvalueOf!Range.front))); } /** diff --git a/std/range/package.d b/std/range/package.d index 4862baecf5d..2b5867b28a9 100644 --- a/std/range/package.d +++ b/std/range/package.d @@ -9146,6 +9146,18 @@ if (isInputRange!Source) assert(equal!`equal(a, b)`(oddsByPairs[3 .. $].take(2), [[13, 15], [17, 19]])); } +// Over an infinite sliceable source, `Chunks.opSlice` returns `typeof(takeExactly(this, 0))`, +// so `isInputRange!Chunks` is evaluated while `Chunks` is still being analysed. That must not +// leave `front` and `popFront` inferred `@system` or impure. +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + auto r = sequence!"a[0] + n * a[1]"(1, 2).chunks(2); + r.popFront(); + assert(r.front.equal([5, 7])); +} + /** diff --git a/std/range/primitives.d b/std/range/primitives.d index a687a77637b..4ddd6469063 100644 --- a/std/range/primitives.d +++ b/std/range/primitives.d @@ -172,11 +172,14 @@ Params: Returns: `true` if R is an input range (possibly with element type `E`), `false` if not */ +// `R.sizeof` lays out a struct before the lambdas below infer its member functions' attributes, +// as `R.init` did: a range trait evaluated inside the struct's own declaration (in a member's +// return type, like `Chunks.opSlice`) would otherwise infer those members impure and `@system`. enum bool isInputRange(R) = - is(typeof(R.init) == R) - && is(typeof((R r) { return r.empty; } (R.init)) == bool) + is(typeof(lvalueOf!R) == R) && is(typeof(R.sizeof)) + && is(typeof((R r) { return r.empty; } (rvalueOf!R)) == bool) && (is(typeof((return ref R r) => r.front)) || is(typeof(ref (return ref R r) => r.front))) - && !is(typeof((R r) { return r.front; } (R.init)) == void) + && !is(typeof((R r) { return r.front; } (rvalueOf!R)) == void) && is(typeof((R r) => r.popFront)); /// ditto @@ -283,6 +286,26 @@ enum bool isInputRange(R, E) = assert(r.equal([ 0, 1, 2 ])); } +// A range type with a member named `init`, here from a mixin template, hides the built-in `.init` +@safe unittest +{ + mixin template SetUp() + { + void init(int v) { i = v; } + } + static struct R + { + int i; + bool empty() const { return i >= 3; } + int front() const { return i; } + void popFront() { ++i; } + R save() { return this; } + mixin SetUp; + } + static assert(isInputRange!R); + static assert(isForwardRange!R); +} + /+ puts the whole raw element `e` into `r`. doPut will not attempt to iterate, slice or transcode `e` in any way shape or form. It will $(B only) @@ -1023,7 +1046,7 @@ Returns: `true` if R is a forward range (possibly with element type `E`), `false` if not */ enum bool isForwardRange(R) = isInputRange!R - && is(typeof((R r) { return r.save; } (R.init)) == R); + && is(typeof((R r) { return r.save; } (rvalueOf!R)) == R); /// ditto enum bool isForwardRange(R, E) = @@ -1091,7 +1114,7 @@ Returns: enum bool isBidirectionalRange(R) = isForwardRange!R && is(typeof((R r) => r.popBack)) && (is(typeof((return ref R r) => r.back)) || is(typeof(ref (return ref R r) => r.back))) - && is(typeof(R.init.back.init) == ElementType!R); + && is(typeof(lvalueOf!R.back.init) == ElementType!R); /// ditto enum bool isBidirectionalRange(R, E) = @@ -1374,7 +1397,11 @@ object `r` of type `R`. For example, `ElementType!(T[])` is */ template ElementType(R) { - static if (is(typeof(R.init.front.init) T)) + // An enum's `.init` is its first member, not a new literal, and only that expression gives + // an enum whose base type is an array a `front`. `R.sizeof`: see `isInputRange`. + static if (is(R == enum) && is(typeof(R.init.front.init) T)) + alias ElementType = T; + else static if (!is(R == enum) && is(typeof(R.sizeof)) && is(typeof(lvalueOf!R.front.init) T)) alias ElementType = T; else alias ElementType = void; @@ -1453,6 +1480,28 @@ template ElementType(R) static assert(is(ElementType!R == E)); } +// The range traits also work when no primitive is a `@property` +@safe unittest +{ + static struct R + { + size_t i; + bool empty() const { return i >= 3; } + int front() const { return cast(int) i; } + void popFront() { ++i; } + R save() { return this; } + int back() const { return cast(int) i; } + void popBack() { ++i; } + size_t length() const { return 3 - i; } + R opSlice(size_t, size_t) { return this; } + } + static assert(isInputRange!R); + static assert(isForwardRange!R); + static assert(isBidirectionalRange!R); + static assert(hasSlicing!R); + static assert(is(ElementType!R == int)); +} + /** The encoding element type of `R`. For narrow strings (`char[]`, `wchar[]` and their qualified variants including `string` and @@ -1764,7 +1813,7 @@ The following expression must be true for `hasSlicing` to be `true`: ---- isForwardRange!R && !(isAutodecodableString!R && !isAggregateType!R) - && is(typeof((R r) { return r[1 .. 1].length; } (R.init)) == size_t) + && is(typeof((R r) { return r[1 .. 1].length; } (rvalueOf!R)) == size_t) && (is(typeof(lvalueOf!R[1 .. 1]) == R) || isInfinite!R) && (!is(typeof(lvalueOf!R[0 .. $])) || is(typeof(lvalueOf!R[0 .. $]) == R)) && (!is(typeof(lvalueOf!R[0 .. $])) || isInfinite!R @@ -1777,7 +1826,7 @@ The following expression must be true for `hasSlicing` to be `true`: */ enum bool hasSlicing(R) = isForwardRange!R && !(isAutodecodableString!R && !isAggregateType!R) - && is(typeof((R r) { return r[1 .. 1].length; } (R.init)) == size_t) + && is(typeof((R r) { return r[1 .. 1].length; } (rvalueOf!R)) == size_t) && (is(typeof(lvalueOf!R[1 .. 1]) == R) || isInfinite!R) && (!is(typeof(lvalueOf!R[0 .. $])) || is(typeof(lvalueOf!R[0 .. $]) == R)) && (!is(typeof(lvalueOf!R[0 .. $])) || isInfinite!R diff --git a/std/traits.d b/std/traits.d index 85343f1a5f3..e12a4f5ce79 100644 --- a/std/traits.d +++ b/std/traits.d @@ -848,13 +848,13 @@ private template fqnType(T, { import std.conv : to; enum fqnType = chain!( - fqnType!(typeof(T.init[0]), qualifiers) ~ "[" ~ to!string(T.length) ~ "]" + fqnType!(typeof(lvalueOf!T[0]), qualifiers) ~ "[" ~ to!string(T.length) ~ "]" ); } else static if (isArray!T) { enum fqnType = chain!( - fqnType!(typeof(T.init[0]), qualifiers) ~ "[]" + fqnType!(typeof(lvalueOf!T[0]), qualifiers) ~ "[]" ); } else static if (isAssociativeArray!T) @@ -2575,7 +2575,7 @@ template hasNested(T) import std.meta : Filter; static if (isStaticArray!T && T.length) - enum hasNested = hasNested!(typeof(T.init[0])); + enum hasNested = hasNested!(typeof(lvalueOf!T[0])); else static if (is(T == class) || is(T == struct) || is(T == union)) { // prevent infinite recursion for class with member of same type @@ -3659,12 +3659,12 @@ template hasElaborateAssign(S) { static if (isStaticArray!S && S.length) { - enum bool hasElaborateAssign = hasElaborateAssign!(typeof(S.init[0])); + enum bool hasElaborateAssign = hasElaborateAssign!(typeof(lvalueOf!S[0])); } else static if (is(S == struct)) { - enum hasElaborateAssign = is(typeof(S.init.opAssign(rvalueOf!S))) || - is(typeof(S.init.opAssign(lvalueOf!S))) || + enum hasElaborateAssign = is(typeof(lvalueOf!S.opAssign(rvalueOf!S))) || + is(typeof(lvalueOf!S.opAssign(lvalueOf!S))) || anySatisfy!(.hasElaborateAssign, FieldTypeTuple!S); } else @@ -4834,6 +4834,23 @@ if (is(T == class)) // Type Conversion //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::// +// A struct's `.init` builds its whole literal, so a function result of the type stands in +// for it. Other operands keep `.init`, whose null and array literals merge differently in `?:`. +// Operands can be values as well as types: `among` passes its values through `CommonType`. +private enum isAggregateOperand(T...) = is(T[0] == struct) || is(T[0] == union); + +private template CommonTypeOfTwo(T...) +{ + static if (isAggregateOperand!(T[0]) && isAggregateOperand!(T[1])) + alias CommonTypeOfTwo = typeof(true ? rvalueOf!(T[0]) : rvalueOf!(T[1])); + else static if (isAggregateOperand!(T[0])) + alias CommonTypeOfTwo = typeof(true ? rvalueOf!(T[0]) : T[1].init); + else static if (isAggregateOperand!(T[1])) + alias CommonTypeOfTwo = typeof(true ? T[0].init : rvalueOf!(T[1])); + else + alias CommonTypeOfTwo = typeof(true ? T[0].init : T[1].init); +} + /** Get the type that all types can be implicitly converted to. Useful e.g. in figuring out an array type from a bunch of initializing @@ -4843,8 +4860,13 @@ types have no common type. template CommonType(T...) { static if (T.length == 1) - alias CommonType = typeof(T[0].init); - else static if (is(typeof(true ? T[0].init : T[1].init) U)) + { + static if (isAggregateOperand!(T[0])) + alias CommonType = typeof(rvalueOf!(T[0])); + else + alias CommonType = typeof(T[0].init); + } + else static if (is(CommonTypeOfTwo!(T[0], T[1]) U)) alias CommonType = CommonType!(U, T[2 .. $]); else alias CommonType = void; @@ -5765,7 +5787,7 @@ Note: Trying to use returned value will result in a */ template BooleanTypeOf(T) { - static if (is(typeof(__traits(getMember, T.init, __traits(getAliasThis, T)[0])) AT) && !is(AT[] == AT)) + static if (is(typeof(__traits(getMember, lvalueOf!T, __traits(getAliasThis, T)[0])) AT) && !is(AT[] == AT)) alias X = BooleanTypeOf!AT; else alias X = OriginalType!T; @@ -5816,7 +5838,7 @@ template BooleanTypeOf(T) */ template IntegralTypeOf(T) { - static if (is(typeof(__traits(getMember, T.init, __traits(getAliasThis, T)[0])) AT) && !is(AT[] == AT)) + static if (is(typeof(__traits(getMember, lvalueOf!T, __traits(getAliasThis, T)[0])) AT) && !is(AT[] == AT)) alias X = IntegralTypeOf!AT; else alias X = OriginalType!T; @@ -5852,7 +5874,7 @@ template IntegralTypeOf(T) */ template FloatingPointTypeOf(T) { - static if (is(typeof(__traits(getMember, T.init, __traits(getAliasThis, T)[0])) AT) && !is(AT[] == AT)) + static if (is(typeof(__traits(getMember, lvalueOf!T, __traits(getAliasThis, T)[0])) AT) && !is(AT[] == AT)) alias X = FloatingPointTypeOf!AT; else alias X = OriginalType!T; @@ -5937,7 +5959,7 @@ template SignedTypeOf(T) */ template CharTypeOf(T) { - static if (is(typeof(__traits(getMember, T.init, __traits(getAliasThis, T)[0])) AT) && !is(AT[] == AT)) + static if (is(typeof(__traits(getMember, lvalueOf!T, __traits(getAliasThis, T)[0])) AT) && !is(AT[] == AT)) alias X = CharTypeOf!AT; else alias X = OriginalType!T; @@ -5978,7 +6000,7 @@ template CharTypeOf(T) */ template StaticArrayTypeOf(T) { - static if (is(typeof(__traits(getMember, T.init, __traits(getAliasThis, T)[0])) AT) && !is(AT[] == AT)) + static if (is(typeof(__traits(getMember, lvalueOf!T, __traits(getAliasThis, T)[0])) AT) && !is(AT[] == AT)) alias X = StaticArrayTypeOf!AT; else alias X = OriginalType!T; @@ -6135,7 +6157,7 @@ template StringTypeOf(T) */ template AssocArrayTypeOf(T) { - static if (is(typeof(__traits(getMember, T.init, __traits(getAliasThis, T)[0])) AT) && !is(AT[] == AT)) + static if (is(typeof(__traits(getMember, lvalueOf!T, __traits(getAliasThis, T)[0])) AT) && !is(AT[] == AT)) alias X = AssocArrayTypeOf!AT; else alias X = OriginalType!T; @@ -6176,7 +6198,7 @@ template BuiltinTypeOf(T) alias BuiltinTypeOf = void; else { - static if (is(typeof(__traits(getMember, T.init, __traits(getAliasThis, T)[0])) AT) && !is(AT[] == AT)) + static if (is(typeof(__traits(getMember, lvalueOf!T, __traits(getAliasThis, T)[0])) AT) && !is(AT[] == AT)) alias X = BuiltinTypeOf!AT; else alias X = OriginalType!T; @@ -7280,7 +7302,11 @@ template isAggregateType(T) * that define `opApply` with a single loop variable, and builtin dynamic, * static and associative arrays. */ -enum bool isIterable(T) = is(typeof({ foreach (elem; T.init) {} })); +// `*p` is for an opApply taking a `@safe` delegate: in a function literal whose attributes are +// inferred, a call to the bodyless `lvalueOf!T` or `rvalueOf!T` makes the loop body's delegate +// `@system`. `rvalueOf!T` is for a non-copyable range, which `foreach` over `*p` would copy. +enum bool isIterable(T) = is(typeof({ T* p = null; foreach (elem; *p) {} })) + || is(typeof({ foreach (elem; rvalueOf!T) {} })); /// @safe unittest @@ -7305,6 +7331,28 @@ enum bool isIterable(T) = is(typeof({ foreach (elem; T.init) {} })); static assert(!isIterable!uint); } +// `opApply` with a `ref` `@safe` delegate, and a range that cannot be copied +@safe unittest +{ + static struct SafeOpApply + { + int opApply(scope int delegate(ref int) @safe dg) @safe { int x; return dg(x); } + } + + static struct NoCopyRange + { + @disable this(this); + enum bool empty = false; + int front() { return 0; } + void popFront() {} + } + + static assert(isIterable!SafeOpApply); + static assert(isIterable!NoCopyRange); + static assert(is(ForeachType!SafeOpApply == int)); + static assert(is(ForeachType!NoCopyRange == int)); +} + /** * Returns true if T is not const or immutable. Note that isMutable is true for * string, or immutable(char)[], because the 'head' is mutable. @@ -8084,15 +8132,32 @@ has both opApply and a range interface. */ template ForeachType(T) { - alias ForeachType = typeof( - (inout int x = 0) + // See `isIterable` for the two forms. + static if (is(typeof({ T* p = null; foreach (elem; *p) {} }))) { - foreach (elem; T.init) + alias ForeachType = typeof( + (inout int x = 0) { - return elem; - } - assert(0); - }()); + T* p = null; + foreach (elem; *p) + { + return elem; + } + assert(0); + }()); + } + else + { + alias ForeachType = typeof( + (inout int x = 0) + { + foreach (elem; rvalueOf!T) + { + return elem; + } + assert(0); + }()); + } } /// @@ -9071,7 +9136,23 @@ enum bool allSameType(Ts...) = Returns: `true` iff the type `T` can be tested in an $(D if)-expression, that is if $(D if (pred(T.init)) {}) is compilable. */ -enum ifTestable(T, alias pred = a => a) = __traits(compiles, { if (pred(T.init)) {} }); +template ifTestable(T, alias pred = a => a) +{ + static if (isAggregateOperand!T) + { + // See `CommonType`: only a struct or union operand avoids `.init`. The literal `T.init` + // is unique, so where `pred` does not take a `T` it converted to any other qualifier of + // `T`; an rvalue of each stands in. + enum testable(U) = __traits(compiles, { if (pred(rvalueOf!U)) {} }); + alias U = Unqual!T; + static if (__traits(compiles, pred(rvalueOf!T))) + enum ifTestable = testable!T; + else + enum ifTestable = anySatisfy!(testable, U, const U, immutable U, shared U, shared const U); + } + else + enum ifTestable = __traits(compiles, { if (pred(T.init)) {} }); +} /// @safe unittest diff --git a/std/typecons.d b/std/typecons.d index 9b096426434..201132c7aac 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -526,8 +526,8 @@ private enum areCompatibleTuples(Tup1, Tup2, string op) = { static foreach (i; 0 .. Tup1.Types.length) {{ - auto lhs = typeof(tup1.field[i]).init; - auto rhs = typeof(tup2.field[i]).init; + auto lhs = rvalueOf!(typeof(tup1.field[i])); + auto rhs = rvalueOf!(typeof(tup2.field[i])); static if (op == "=") lhs = rhs; else @@ -676,7 +676,7 @@ if (distinctFieldNames!(Specs)) { static foreach (i; 0 .. Types.length) // Rely on the field layout { - static assert(typeof(return).init.tupleof[i].offsetof == + static assert(typeof(return).tupleof[i].offsetof == expand[i].offsetof); } return *cast(typeof(return)*) &(field[0]); @@ -2330,10 +2330,13 @@ template tuple(Names...) Returns: true if `T` is a `Tuple` type, false otherwise. */ -enum isTuple(T) = __traits(compiles, +// The `is` covers qualified tuples whose fields have indirections, which the call below +// cannot convert to the mutable `Tuple`. +enum isTuple(T) = is(Unqual!T == Tuple!Specs, Specs...) || + __traits(compiles, { void f(Specs...)(Tuple!Specs tup) {} - f(T.init); + f(rvalueOf!T); } ); /// @@ -2350,6 +2353,8 @@ enum isTuple(T) = __traits(compiles, { static assert(isTuple!(const Tuple!(int))); static assert(isTuple!(immutable Tuple!(int))); + static assert(isTuple!(const Tuple!(int[], int))); + static assert(isTuple!(immutable Tuple!(int*))); static assert(!isTuple!(int)); static assert(!isTuple!(const int)); @@ -2501,11 +2506,11 @@ struct Rebindable(T) if (!is(T == class) && !is(T == interface) && !isDynamicArray!T && !isAssociativeArray!T) { private: - static if (isAssignable!(typeof(cast() T.init))) + static if (isAssignable!(typeof(cast() rvalueOf!T))) { enum useQualifierCast = true; - typeof(cast() T.init) data; + typeof(cast() rvalueOf!T) data; } else { @@ -3059,11 +3064,11 @@ Rebindable!T rebindable(T)(Rebindable!T obj) package(std) struct Rebindable2(T) { private: - static if (isAssignable!(typeof(cast() T.init))) + static if (isAssignable!(typeof(cast() rvalueOf!T))) { enum useQualifierCast = true; - typeof(cast() T.init) data; + typeof(cast() rvalueOf!T) data; } else { @@ -5369,7 +5374,7 @@ template apply(alias fun) auto apply(T)(auto ref T t) if (isInstanceOf!(Nullable, T)) { - alias FunType = typeof(unaryFun!fun(T.init.get)); + alias FunType = typeof(unaryFun!fun(rvalueOf!T.get)); enum MustWrapReturn = !isInstanceOf!(Nullable, FunType);