diff options
author | Iain Buclaw <ibuclaw@gdcproject.org> | 2021-12-05 17:11:12 +0100 |
---|---|---|
committer | Iain Buclaw <ibuclaw@gdcproject.org> | 2021-12-09 00:58:58 +0100 |
commit | 0fb57034770aa20adced4d176f34ca611c2945bf (patch) | |
tree | 1f5735c8b4f25aa4a290e5ae8124713c24f98359 /libphobos/src/std/algorithm/comparison.d | |
parent | c15aa46cca0649b68613d3292cf71c7cc57ef78f (diff) | |
download | gcc-0fb57034770aa20adced4d176f34ca611c2945bf.zip gcc-0fb57034770aa20adced4d176f34ca611c2945bf.tar.gz gcc-0fb57034770aa20adced4d176f34ca611c2945bf.tar.bz2 |
d: Merge upstream dmd 568496d5b, druntime 178c44ff, phobos 574bf883b.
D front-end changes:
- Import dmd v2.098.0
- New ImportC module for compiling preprocessed C11 code into D.
- New -ftransition=in switch.
- Improved handling of new 'noreturn' type.
Druntime changes:
- Import druntime v2.098.0
- Fix broken import in core.sys.linux.perf_event module (PR103558).
Phobos changes:
- Import phobos v2.098.0
- All sources are now compiled with -fpreview=fieldwise.
gcc/d/ChangeLog:
* dmd/MERGE: Merge upstream dmd 568496d5b.
* Make-lang.in (D_FRONTEND_OBJS): Add d/common-file.o,
d/common-outbuffer.o, d/common-string.o, d/file_manager.o,
d/importc.o. Remove d/root-outbuffer.o.
(d/common-%.o): New recipe.
* d-builtins.cc (build_frontend_type): Update for new front-end
interface.
(d_build_d_type_nodes): Set noreturn_type_node.
* d-codegen.cc (d_build_call): Don't call function if one of the
arguments is type 'noreturn'.
(build_vthis_function): Propagate TYPE_QUAL_VOLATILE from original
function type.
* d-frontend.cc (eval_builtin): Update signature.
(getTypeInfoType): Likewise.
(toObjFile): New function.
* d-gimplify.cc (d_gimplify_call_expr): Always evaluate arguments from
left to right.
* d-lang.cc (d_handle_option): Handle OPT_ftransition_in.
(d_parse_file): Don't generate D main if it is declared in user code.
* d-tree.h (CALL_EXPR_ARGS_ORDERED): Remove.
(enum d_tree_index): Add DTI_BOTTOM_TYPE.
(noreturn_type_node): New.
* decl.cc (apply_pragma_crt): Remove.
(DeclVisitor::visit): Update for new front-end interface.
(DeclVisitor::visit (PragmaDeclaration *)): Don't handle
crt_constructor and crt_destructor pragmas.
(DeclVisitor::visit (VarDeclaration *)): Don't generate declarations
of type 'noreturn'.
(DeclVisitor::visit (FuncDeclaration *)): Stop adding parameters when
'noreturn' type has been encountered.
(get_symbol_decl): Set DECL_STATIC_CONSTRUCTOR and
DECL_STATIC_DESTRUCTOR on decl node if requested.
(aggregate_initializer_decl): Update for new front-end interface.
* expr.cc (ExprVisitor::visit (CallExp *)): Always use the 'this'
object as the result of calling any constructor function.
(ExprVisitor::visit): Update for new front-end interface.
* gdc.texi (Runtime Options): Document -fmain and -ftransition=in.
* lang.opt (ftransition=in): New option.
* modules.cc (get_internal_fn): Update for new front-end interface.
* types.cc (TypeVisitor::visit): Likewise.
(TypeVisitor::visit (TypeNoreturn *)): Return noreturn_type_node.
(TypeVisitor::visit (TypeFunction *)): Stop adding parameters when
'notreturn' type has been encountered. Qualify function types that
return 'noreturn' as TYPE_QUAL_VOLATILE.
libphobos/ChangeLog:
PR d/103558
* libdruntime/MERGE: Merge upstream druntime 178c44ff.
* libdruntime/Makefile.am (DRUNTIME_DSOURCES_LINUX): Add
core/sys/linux/syscalls.d.
(DRUNTIME_DSOURCES_OPENBSD): Add core/sys/openbsd/pthread_np.d.
* libdruntime/Makefile.in: Regenerate.
* src/MERGE: Merge upstream phobos 574bf883b.
* src/Makefile.am (D_EXTRA_DFLAGS): Add -fpreview=fieldwise.
* src/Makefile.in: Regenerate.
* testsuite/libphobos.exceptions/assert_fail.d: Update test.
* testsuite/libphobos.betterc/test22336.d: New test.
Diffstat (limited to 'libphobos/src/std/algorithm/comparison.d')
-rw-r--r-- | libphobos/src/std/algorithm/comparison.d | 401 |
1 files changed, 283 insertions, 118 deletions
diff --git a/libphobos/src/std/algorithm/comparison.d b/libphobos/src/std/algorithm/comparison.d index c952544..2fcc2ba 100644 --- a/libphobos/src/std/algorithm/comparison.d +++ b/libphobos/src/std/algorithm/comparison.d @@ -58,10 +58,10 @@ T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) */ module std.algorithm.comparison; -import std.functional : unaryFun, binaryFun; +import std.functional : unaryFun, binaryFun, lessThan, greaterThan; import std.range.primitives; import std.traits; -import std.meta : allSatisfy; +import std.meta : allSatisfy, anySatisfy; import std.typecons : tuple, Tuple, Flag, Yes; import std.internal.attributes : betterC; @@ -247,7 +247,7 @@ auto castSwitch(choices...)(Object switchObject) bool result = true; foreach (index, choice; choices) { - result &= is(ReturnType!choice == void); + result &= is(ReturnType!choice : void); // void or noreturn } return result; }(); @@ -514,9 +514,54 @@ auto castSwitch(choices...)(Object switchObject) ) == "derived from I"); } -/** Clamps a value into the given bounds. +// https://issues.dlang.org/show_bug.cgi?id=22384 +@system unittest +{ + // Use explicit methods to enforce return types + static void objectSkip(Object) {} + static void defaultSkip() {} + + static noreturn objectError(Object) { assert(false); } + static noreturn defaultError() { assert(false); } + + { + alias test = castSwitch!(objectSkip, defaultError); + static assert(is(ReturnType!test == void)); + }{ + alias test = castSwitch!(objectError, defaultSkip); + static assert(is(ReturnType!test == void)); + }{ + alias test = castSwitch!(objectError, defaultError); + static assert(is(ReturnType!test == noreturn)); + } + + // Also works with non-void handlers + static int objectValue(Object) { return 1;} + static int defaultValue() { return 2; } + + { + alias test = castSwitch!(objectValue, defaultError); + static assert(is(ReturnType!test == int)); + }{ + alias test = castSwitch!(objectError, defaultValue); + static assert(is(ReturnType!test == int)); + } + + // No confusion w.r.t. void callbacks + alias FP = void function(); + static FP objectFunc(Object) { return &defaultSkip; } + static FP defaultFunc() { return &defaultSkip; } + + { + alias test = castSwitch!(objectFunc, defaultError); + static assert(is(ReturnType!test == FP)); + }{ + alias test = castSwitch!(objectError, defaultFunc); + static assert(is(ReturnType!test == FP)); + } +} -This function is equivalent to `max(lower, min(upper, val))`. +/** Clamps `val` into the given bounds. Result has the same type as `val`. Params: val = The value to _clamp. @@ -524,20 +569,22 @@ Params: upper = The _upper bound of the _clamp. Returns: - Returns `val`, if it is between `lower` and `upper`. - Otherwise returns the nearest of the two. - + `lower` if `val` is less than `lower`, `upper` if `val` is greater than + `upper`, and `val` in all other cases. Comparisons are made + correctly (using $(REF lessThan, std,functional) and the return value + is converted to the return type using the standard integer coversion rules + $(REF greaterThan, std,functional)) even if the signedness of `T1`, `T2`, + and `T3` are different. */ -auto clamp(T1, T2, T3)(T1 val, T2 lower, T3 upper) -if (is(typeof(max(min(val, upper), lower)))) +T1 clamp(T1, T2, T3)(T1 val, T2 lower, T3 upper) +if (is(typeof(val.lessThan(lower) ? lower : val.greaterThan(upper) ? upper : val) : T1)) in { - import std.functional : greaterThan; assert(!lower.greaterThan(upper), "Lower can't be greater than upper."); } do { - return max(min(val, upper), lower); + return val.lessThan(lower) ? lower : val.greaterThan(upper) ? upper : val; } /// @@ -550,6 +597,10 @@ do assert(clamp(1, 1, 1) == 1); assert(clamp(5, -1, 2u) == 2); + + auto x = clamp(42, uint.max, uint.max); + static assert(is(typeof(x) == int)); + assert(x == -1); } @safe unittest @@ -564,7 +615,7 @@ do // mixed sign a = -5; uint f = 5; - static assert(is(typeof(clamp(f, a, b)) == int)); + static assert(is(typeof(clamp(f, a, b)) == uint)); assert(clamp(f, a, b) == f); // similar type deduction for (u)long static assert(is(typeof(clamp(-1L, -2L, 2UL)) == long)); @@ -874,101 +925,121 @@ nothrow pure @safe unittest // equal /** -Compares two ranges for equality, as defined by predicate `pred` +Compares two or more ranges for equality, as defined by predicate `pred` (which is `==` by default). */ template equal(alias pred = "a == b") { /++ - Compares two ranges for equality. The ranges may have - different element types, as long as `pred(r1.front, r2.front)` - evaluates to `bool`. - Performs $(BIGOH min(r1.length, r2.length)) evaluations of `pred`. + Compares two or more ranges for equality. The ranges may have + different element types, as long as all are comparable by means of + the `pred`. + Performs $(BIGOH min(rs[0].length, rs[1].length, ...)) evaluations of `pred`. However, if + `equal` is invoked with the default predicate, the implementation may take the liberty + to use faster implementations that have the theoretical worst-case + $(BIGOH max(rs[0].length, rs[1].length, ...)). At least one of the ranges must be finite. If one range involved is infinite, the result is (statically known to be) `false`. - If the two ranges are different kinds of UTF code unit (`char`, `wchar`, or - `dchar`), then the arrays are compared using UTF decoding to avoid + If the ranges have different kinds of UTF code unit (`char`, `wchar`, or + `dchar`), then they are compared using UTF decoding to avoid accidentally integer-promoting units. Params: - r1 = The first range to be compared. - r2 = The second range to be compared. + rs = The ranges to be compared. Returns: - `true` if and only if the two ranges compare _equal element + `true` if and only if all ranges compare _equal element for element, according to binary predicate `pred`. +/ - bool equal(Range1, Range2)(Range1 r1, Range2 r2) - if (isInputRange!Range1 && isInputRange!Range2 && - !(isInfinite!Range1 && isInfinite!Range2) && - is(typeof(binaryFun!pred(r1.front, r2.front)))) + bool equal(Ranges...)(Ranges rs) + if (rs.length > 1 + && allSatisfy!(isInputRange, Ranges) + && !allSatisfy!(isInfinite, Ranges) + && is(typeof(binaryFun!pred(rs[0].front, rs[1].front))) + && (rs.length == 2 || is(typeof(equal!pred(rs[1 .. $])) == bool)) + ) { - // Use code points when comparing two ranges of UTF code units that aren't - // the same type. This is for backwards compatibility with autodecode - // strings. - enum useCodePoint = - isSomeChar!(ElementEncodingType!Range1) && isSomeChar!(ElementEncodingType!Range2) && - ElementEncodingType!Range1.sizeof != ElementEncodingType!Range2.sizeof; - - static if (useCodePoint) + alias ElementEncodingTypes = staticMap!(ElementEncodingType, Ranges); + enum differentSize(T) = T.sizeof != ElementEncodingTypes[0].sizeof; + enum useCodePoint = allSatisfy!(isSomeChar, ElementEncodingTypes) && + anySatisfy!(differentSize, ElementEncodingTypes); + enum bool comparableWithEq(alias r) = is(typeof(rs[0] == r)); + + static if (anySatisfy!(isInfinite, Ranges)) { - import std.utf : byDchar; - return equal(r1.byDchar, r2.byDchar); + return false; } - else + else static if (useCodePoint) { - static if (isInfinite!Range1 || isInfinite!Range2) - { - // No finite range can be ever equal to an infinite range. - return false; - } - // Detect default pred and compatible dynamic arrays. - else static if (is(typeof(pred) == string) && pred == "a == b" && - isArray!Range1 && isArray!Range2 && is(typeof(r1 == r2))) - { - return r1 == r2; - } - // If one of the arguments is a string and the other isn't, then auto-decoding - // can be avoided if they have the same ElementEncodingType. - else static if (is(typeof(pred) == string) && pred == "a == b" && - isAutodecodableString!Range1 != isAutodecodableString!Range2 && - is(immutable ElementEncodingType!Range1 == immutable ElementEncodingType!Range2)) + import std.utf : byDchar; + static bool allByDchar(size_t done, Ranges...)(auto ref Ranges rs) { - import std.utf : byCodeUnit; - - static if (isAutodecodableString!Range1) - return equal(r1.byCodeUnit, r2); + static if (done == rs.length) + return equalLoop(rs); else - return equal(r2.byCodeUnit, r1); - } - // Try a fast implementation when the ranges have comparable lengths. - else static if (hasLength!Range1 && hasLength!Range2 && is(typeof(r1.length == r2.length))) - { - immutable len1 = r1.length; - immutable len2 = r2.length; - if (len1 != len2) return false; //Short circuit return - - // Lengths are the same, so we need to do an actual comparison. - // Good news is we can squeeze out a bit of performance by not checking if r2 is empty. - for (; !r1.empty; r1.popFront(), r2.popFront()) - { - if (!binaryFun!(pred)(r1.front, r2.front)) return false; - } - return true; + return allByDchar!(done + 1)(rs[0 .. done], rs[done].byDchar, rs[done + 1 .. $]); } + return allByDchar!0(rs); + } + else static if (is(typeof(pred) == string) && pred == "a == b" && + allSatisfy!(isArray, Ranges) && allSatisfy!(comparableWithEq, rs)) + { + static foreach (r; rs[1 .. $]) + if (rs[0] != r) + return false; + return true; + } + // if one of the arguments is a string and the other isn't, then auto-decoding + // can be avoided if they have the same ElementEncodingType + // TODO: generalize this + else static if (rs.length == 2 && is(typeof(pred) == string) && pred == "a == b" && + isAutodecodableString!(Ranges[0]) != isAutodecodableString!(Ranges[1]) && + is(immutable ElementEncodingType!(Ranges[0]) == immutable ElementEncodingType!(Ranges[1]))) + { + import std.utf : byCodeUnit; + static if (isAutodecodableString!(Ranges[0])) + return equal(rs[0].byCodeUnit, rs[1]); else + return equal(rs[1].byCodeUnit, rs[0]); + } + else + { + static foreach (i, R; Ranges) { - //Generic case, we have to walk both ranges making sure neither is empty - for (; !r1.empty; r1.popFront(), r2.popFront()) + static if (hasLength!R) { - if (r2.empty || !binaryFun!(pred)(r1.front, r2.front)) return false; + static if (!is(typeof(firstLength))) + { + // Found the first range that has length + auto firstLength = rs[i].length; + } + else + { + // Compare the length of the current range against the first with length + if (firstLength != rs[i].length) + return false; + } } - return r2.empty; } + return equalLoop(rs); } } + + private bool equalLoop(Rs...)(Rs rs) + { + for (; !rs[0].empty; rs[0].popFront) + static foreach (r; rs[1 .. $]) + if (r.empty || !binaryFun!pred(rs[0].front, r.front)) + return false; + else + r.popFront; + static foreach (r; rs[1 .. $]) + if (!r.empty) + return false; + return true; + } } /// @@ -992,6 +1063,31 @@ template equal(alias pred = "a == b") assert(equal!isClose(b[], c[])); } +@safe @nogc unittest +{ + import std.algorithm.comparison : equal; + import std.math.operations : isClose; + + auto s1 = "abc", s2 = "abc"w; + assert(equal(s1, s2, s2)); + assert(equal(s1, s2, s2, s1)); + assert(!equal(s1, s2, s2[1 .. $])); + + int[4] a = [ 1, 2, 4, 3 ]; + assert(!equal(a[], a[1..$], a[])); + assert(equal(a[], a[], a[])); + assert(equal!((a, b) => a == b)(a[], a[], a[])); + + // different types + double[4] b = [ 1.0, 2, 4, 3]; + assert(!equal(a[], b[1..$], b[])); + assert(equal(a[], b[], a[], b[])); + + // predicated: ensure that two vectors are approximately equal + double[4] c = [ 1.0000000005, 2, 4, 3]; + assert(equal!isClose(b[], c[], b[])); +} + /++ Tip: `equal` can itself be used as a predicate to other functions. This can be very useful when the element type of a range is itself a @@ -1748,21 +1844,26 @@ store the lowest values. // mismatch /** -Sequentially compares elements in `r1` and `r2` in lockstep, and +Sequentially compares elements in `rs` in lockstep, and stops at the first mismatch (according to `pred`, by default equality). Returns a tuple with the reduced ranges that start with the -two mismatched values. Performs $(BIGOH min(r1.length, r2.length)) +two mismatched values. Performs $(BIGOH min(r[0].length, r[1].length, ...)) evaluations of `pred`. */ -Tuple!(Range1, Range2) -mismatch(alias pred = "a == b", Range1, Range2)(Range1 r1, Range2 r2) -if (isInputRange!(Range1) && isInputRange!(Range2)) +Tuple!(Ranges) +mismatch(alias pred = (a, b) => a == b, Ranges...)(Ranges rs) +if (rs.length >= 2 && allSatisfy!(isInputRange, Ranges)) { - for (; !r1.empty && !r2.empty; r1.popFront(), r2.popFront()) + loop: for (; !rs[0].empty; rs[0].popFront) { - if (!binaryFun!(pred)(r1.front, r2.front)) break; + static foreach (r; rs[1 .. $]) + { + if (r.empty || !binaryFun!pred(rs[0].front, r.front)) + break loop; + r.popFront; + } } - return tuple(r1, r2); + return tuple(rs); } /// @@ -1773,6 +1874,12 @@ if (isInputRange!(Range1) && isInputRange!(Range2)) auto m = mismatch(x[], y[]); assert(m[0] == x[3 .. $]); assert(m[1] == y[3 .. $]); + + auto m2 = mismatch(x[], y[], x[], y[]); + assert(m2[0] == x[3 .. $]); + assert(m2[1] == y[3 .. $]); + assert(m2[2] == x[3 .. $]); + assert(m2[3] == y[3 .. $]); } @safe @nogc unittest @@ -1925,50 +2032,91 @@ auto predSwitch(alias pred = "a == b", T, R ...)(T switchExpression, lazy R choi } /** -Checks if the two ranges have the same number of elements. This function is +Checks if two or more ranges have the same number of elements. This function is optimized to always take advantage of the `length` member of either range if it exists. -If both ranges have a length member, this function is $(BIGOH 1). Otherwise, -this function is $(BIGOH min(r1.length, r2.length)). +If all ranges have a `length` member or at least one is infinite, +`_isSameLength`'s complexity is $(BIGOH 1). Otherwise, complexity is +$(BIGOH n), where `n` is the smallest of the lengths of ranges with unknown +length. -Infinite ranges are considered of the same length. An infinite range has never the same length as a -finite range. +Infinite ranges are considered of the same length. An infinite range has never +the same length as a finite range. Params: - r1 = a finite $(REF_ALTTEXT input range, isInputRange, std,range,primitives) - r2 = a finite $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + rs = two or more $(REF_ALTTEXT input ranges, isInputRange, std,range,primitives) Returns: `true` if both ranges have the same length, `false` otherwise. */ -bool isSameLength(Range1, Range2)(Range1 r1, Range2 r2) -if (isInputRange!Range1 && isInputRange!Range2) +bool isSameLength(Ranges...)(Ranges rs) +if (allSatisfy!(isInputRange, Ranges)) { - static if (isInfinite!Range1 || isInfinite!Range2) - { - return isInfinite!Range1 && isInfinite!Range2; - } - else static if (hasLength!(Range1) && hasLength!(Range2)) + static if (anySatisfy!(isInfinite, Ranges)) { - return r1.length == r2.length; + return allSatisfy!(isInfinite, Ranges); } - else static if (hasLength!(Range1) && !hasLength!(Range2)) + else static if (anySatisfy!(hasLength, Ranges)) { - return r2.walkLength(r1.length + 1) == r1.length; - } - else static if (!hasLength!(Range1) && hasLength!(Range2)) - { - return r1.walkLength(r2.length + 1) == r2.length; + // Compute the O(1) length + auto baselineLength = size_t.max; + static foreach (i, R; Ranges) + { + static if (hasLength!R) + { + if (baselineLength == size_t.max) + baselineLength = rs[i].length; + else if (rs[i].length != baselineLength) + return false; + } + } + // Iterate all ranges without known length + foreach (_; 0 .. baselineLength) + static foreach (i, R; Ranges) + { + static if (!hasLength!R) + { + // All must be non-empty + if (rs[i].empty) + return false; + rs[i].popFront; + } + } + static foreach (i, R; Ranges) + { + static if (!hasLength!R) + { + // All must be now empty + if (!rs[i].empty) + return false; + } + } + return true; } else { - for (; !r1.empty; r1.popFront, r2.popFront) - { - if (r2.empty) - return false; - } - return r2.empty; + // All have unknown length, iterate in lockstep + for (;;) + static foreach (i, r; rs) + { + if (r.empty) + { + // One is empty, so all must be empty + static if (i != 0) + { + return false; + } + else + { + static foreach (j, r1; rs[1 .. $]) + if (!r1.empty) + return false; + return true; + } + } + r.popFront; + } } } @@ -1976,34 +2124,42 @@ if (isInputRange!Range1 && isInputRange!Range2) @safe nothrow pure unittest { assert(isSameLength([1, 2, 3], [4, 5, 6])); + assert(isSameLength([1, 2, 3], [4, 5, 6], [7, 8, 9])); assert(isSameLength([0.3, 90.4, 23.7, 119.2], [42.6, 23.6, 95.5, 6.3])); assert(isSameLength("abc", "xyz")); + assert(isSameLength("abc", "xyz", [1, 2, 3])); int[] a; int[] b; assert(isSameLength(a, b)); + assert(isSameLength(a, b, a, a, b, b, b)); assert(!isSameLength([1, 2, 3], [4, 5])); + assert(!isSameLength([1, 2, 3], [4, 5, 6], [7, 8])); assert(!isSameLength([0.3, 90.4, 23.7], [42.6, 23.6, 95.5, 6.3])); assert(!isSameLength("abcd", "xyz")); + assert(!isSameLength("abcd", "xyz", "123")); + assert(!isSameLength("abcd", "xyz", "1234")); } // Test CTFE @safe @nogc pure @betterC unittest { - enum result1 = isSameLength([1, 2, 3], [4, 5, 6]); - static assert(result1); - - enum result2 = isSameLength([0.3, 90.4, 23.7], [42.6, 23.6, 95.5, 6.3]); - static assert(!result2); + static assert(isSameLength([1, 2, 3], [4, 5, 6])); + static assert(isSameLength([1, 2, 3], [4, 5, 6], [7, 8, 9])); + static assert(!isSameLength([0.3, 90.4, 23.7], [42.6, 23.6, 95.5, 6.3])); + static assert(!isSameLength([1], [0.3, 90.4], [42])); } @safe @nogc pure unittest { import std.range : only; assert(isSameLength(only(1, 2, 3), only(4, 5, 6))); + assert(isSameLength(only(1, 2, 3), only(4, 5, 6), only(7, 8, 9))); assert(isSameLength(only(0.3, 90.4, 23.7, 119.2), only(42.6, 23.6, 95.5, 6.3))); assert(!isSameLength(only(1, 3, 3), only(4, 5))); + assert(!isSameLength(only(1, 3, 3), only(1, 3, 3), only(4, 5))); + assert(!isSameLength(only(1, 3, 3), only(4, 5), only(1, 3, 3))); } @safe nothrow pure unittest @@ -2033,6 +2189,15 @@ if (isInputRange!Range1 && isInputRange!Range2) DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Input) r11; auto r12 = new ReferenceInputRange!int([1, 2, 3, 4, 5, 6, 7, 8]); assert(!isSameLength(r11, r12)); + + import std.algorithm.iteration : filter; + + assert(isSameLength(filter!"a >= 1"([1, 2, 3]), [4, 5, 6])); + assert(!isSameLength(filter!"a > 1"([1, 2, 3]), [4, 5, 6])); + + assert(isSameLength(filter!"a > 1"([1, 2, 3]), filter!"a > 4"([4, 5, 6]))); + assert(isSameLength(filter!"a > 1"([1, 2, 3]), + filter!"a > 4"([4, 5, 6]), filter!"a >= 5"([4, 5, 6]))); } // Still functional but not documented anymore. |