Skip to content

Commit

Permalink
Fix Issue 16628 - std.algorithm.equal for known empty or infinite ranges
Browse files Browse the repository at this point in the history
* If one of the ranges has `Range.empty == true`, we can define `equal`
  even when each `front` is not comparable.
* If one range is infinite and the other defines `length`, return false.
* If both are infinite, cause a compile-time error.
  • Loading branch information
ntrel committed Oct 20, 2016
1 parent 336f5c4 commit 2a45a14
Showing 1 changed file with 66 additions and 17 deletions.
83 changes: 66 additions & 17 deletions std/algorithm/comparison.d
Expand Up @@ -710,28 +710,52 @@ Compares two ranges for equality, as defined by predicate $(D pred)
*/
template equal(alias pred = "a == b")
{
/// Internal template - returns true if `R.empty == true`.
enum isEmptyRange(R) =
isInputRange!R && __traits(compiles, {static assert(R.empty);});

enum hasFixedLength(T) = hasLength!T || isNarrowString!T;

/++
This function compares to ranges for equality. The ranges may have
different element types, as long as $(D pred(a, b)) evaluates to $(D bool)
for $(D a) in $(D r1) and $(D b) in $(D r2).
Compares two ranges for equality. The ranges may have
different element types, as long as $(D pred(r1.front, r2.front))
evaluates to $(D bool).
Performs $(BIGOH min(r1.length, r2.length)) evaluations of $(D pred).
Alternatively, if `Range1` or `Range2` satisfy $(LREF isEmptyRange),
`pred(r1.front, r2.front)` will $(B not) be evaluated.
Params:
r1 = The first range to be compared.
r2 = The second range to be compared.
Returns:
$(D true) if and only if the two ranges compare equal element
$(D true) if and only if the two ranges compare _equal element
for element, according to binary predicate $(D pred).
See_Also:
$(HTTP sgi.com/tech/stl/_equal.html, STL's _equal)
+/
bool equal(Range1, Range2)(Range1 r1, Range2 r2)
if (isInputRange!Range1 && isInputRange!Range2 && is(typeof(binaryFun!pred(r1.front, r2.front))))
if (isInputRange!Range1 && isInputRange!Range2 &&
(is(typeof(binaryFun!pred(r1.front, r2.front))) ||
isEmptyRange!Range1 || isEmptyRange!Range2))
{
//Start by detecting default pred and compatible dynamicarray.
static if (is(typeof(pred) == string) && pred == "a == b" &&
static assert(!(isInfinite!Range1 && isInfinite!Range2),
"Both ranges are known to be infinite");

//No pred calls necessary
static if (isEmptyRange!Range1 || isEmptyRange!Range2)
{
return r1.empty && r2.empty;
}
else static if ((isInfinite!Range1 && hasFixedLength!Range2) ||
(hasFixedLength!Range1 && isInfinite!Range2))
{
return false;
}
//Detect default pred and compatible dynamic array
else static if (is(typeof(pred) == string) && pred == "a == b" &&
isArray!Range1 && isArray!Range2 && is(typeof(r1 == r2)))
{
return r1 == r2;
Expand All @@ -746,16 +770,12 @@ template equal(alias pred = "a == b")

static if (isAutodecodableString!Range1)
{
auto codeUnits = r1.byCodeUnit;
alias other = r2;
return equal(r1.byCodeUnit, r2);
}
else
{
auto codeUnits = r2.byCodeUnit;
alias other = r1;
return equal(r2.byCodeUnit, r1);
}

return equal(codeUnits, other);
}
//Try a fast implementation when the ranges have comparable lengths
else static if (hasLength!Range1 && hasLength!Range2 && is(typeof(r1.length == r2.length)))
Expand All @@ -765,7 +785,7 @@ template equal(alias pred = "a == b")
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 sqeeze out a bit of performance by not checking if r2 is empty
//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;
Expand All @@ -780,7 +800,8 @@ template equal(alias pred = "a == b")
if (r2.empty) return false;
if (!binaryFun!(pred)(r1.front, r2.front)) return false;
}
return r2.empty;
static if (!isInfinite!Range1)
return r2.empty;
}
}
}
Expand Down Expand Up @@ -880,9 +901,13 @@ range of range (of range...) comparisons.
cir = new ReferenceInputRange!int([1, 2, 8, 1]);
assert(!equal(cir, cfr));

//Test with an infinte range
ReferenceInfiniteForwardRange!int ifr = new ReferenceInfiniteForwardRange!int;
//Test with an infinite range
auto ifr = new ReferenceInfiniteForwardRange!int;
assert(!equal(a, ifr));
assert(!equal(ifr, a));
//Test InputRange without length
assert(!equal(ifr, cir));
assert(!equal(cir, ifr));
}

@safe pure unittest
Expand All @@ -897,6 +922,30 @@ range of range (of range...) comparisons.
assert(equal("æøå"d, "æøå".byDchar));
}

@safe pure unittest
{
struct R(T, bool _empty) {
enum empty = _empty;
@property T front(){assert(0);}
void popFront(){assert(0);}
}
alias I = R!(char, false);
// infinite R1 should have compatible elements even with R2.length defined
static assert(!__traits(compiles, I().equal([cast(void*)null])));
static assert(!__traits(compiles, I().equal(I())));
// strings have fixed length so don't compare elements
assert(!I().equal("foo"));
assert(!"bar".equal(I()));

alias E = R!(void*, true);
assert(E().equal(E()));
// incompatible front types should work with E
assert(E().equal(""));
assert("".equal(E()));
assert(!E().equal("foo"));
assert(!"bar".equal(E()));
}

// MaxType
private template MaxType(T...)
if (T.length >= 1)
Expand Down

0 comments on commit 2a45a14

Please sign in to comment.