Example Program and Compiler Errors
The following code produces two compiler errors (MSVC 2022 compiling with /std:c latest
because <ranges>
isn't yet enabled for /std:c 20
at time of writing this question):
error C2672: 'operator __surrogate_func': no matching overloaded function found
error C7602: 'std::ranges::_Equal_fn::operator ()': the associated constraints are not satisfied
#include <iostream>
#include <ranges>
#include <algorithm>
#include <string>
#include <cctype>
int main() {
using std::views::transform;
std::vector<std::string> foo{ "THe", "CaT", "SaT", "oN", "THe", "MaT" };
std::vector<std::string> bar{ "The", "Cat", "Sat", "On", "The", "Mat" };
bool are_equal = std::ranges::equal(
foo | transform(transform(std::tolower)),
bar | transform(transform(std::tolower)),
std::ranges::equal
);
std::cout
<< "vectors 'foo' and 'bar' are equal? - "
<< std::boolalpha << are_equal << std::endl;
}
Analysis
My guess regarding the first error __surrogate_func
appears to be attempting to use operator==
between the two transform_view::iterator
s
(As far as I can tell, the iterator defines operator==
).
The second error appears to relate to a constraint for std::ranges::equal::operator()
.
As far as I can tell, a constraint does not seem to be satisfied by transform_view
, or perhaps more specifically, transform_view::iterator
.
Looking at the source for ..\include\algorithm
, ranges::equal
is an instance of class _Equal_fn
, with _Equal_fn::operator()
having constraint
requires indirectly_comparable<iterator_t<_Rng1>, iterator_t<_Rng2>
.
I'm struggling to find anything on cppreference/ranges/transform_view to suggest that the transform_view::iterator
is required to conform to this constraint, so I assume it's not supposed to.
However the implementation _Equal_fn
clearly behaves as-expected when bypassed using a lambda without any constraints to wrap ranges::equal
. This alternative implementation compiles in MSVC and produces the expected result:
auto lambda_equal = [](const auto& lhs, const auto& rhs) {
return std::ranges::equal(lhs, rhs);
};
// Now it compiles (MSVC )
bool are_equal = std::ranges::equal(
foo | transform(transform(std::tolower)),
bar | transform(transform(std::tolower)),
lambda_equal
);
(Which could be made a little more generic using a python-esque decorator pattern to make it generic enough for any other binary functionoid):
constexpr auto binary_invocable = [](const auto& fn) {
return [&fn](const auto& lhs, const auto& rhs) {
return fn(lhs, rhs);
};
};
// Now it compiles (MSVC )
bool are_equal = std::ranges::equal(
foo | transform(transform(std::tolower)),
bar | transform(transform(std::tolower)),
binary_invocable(std::ranges::equal)
);
Question:
On the basis that the problem appears to be with the constraint and not a lack of an iterator::operator== ()
, is there any way, using standard library functions/functors to either wrap range::equal
or to wrap transform_view
, as a means to satisfying the indirectly_comparable<iterator_t<Rng1>, iterator_t<Rng1>
constraint?
CodePudding user response:
Can
ranges::equal
be used as a predicate for a pair of transform_view ranges?
No, it cannot. This is because ranges::equal
is colloquially referred to as a niebloid (cppref, blog).
In the standard, they are defined in [algorithms.requirements]/2:
The entities defined in the
std::ranges
namespace in this Clause are not found by argument-dependent name lookup ([basic.lookup.argdep]). When found by unqualified ([basic.lookup.unqual]) name lookup for the postfix-expression in a function call ([expr.call]), they inhibit argument-dependent name lookup.
That's it. Namely, ranges::equal
is still presented as a function template - just as this special kind of function template that inhibits, and is not found by, argument-dependent lookup.
The only way to implement that in C today is as a function object. But they're not specified as function objects, so you cannot rely on function-object-like properties. Like copyability. libstdc simply implements them as empty function objects, but MSVC deliberately chooses to not provide any functionality not specified in the standard. Hence in MSVC's implementation, ranges::equal
is not copyable -- because it is not specified to be an object, it's specified as a function template, and you cannot use function templates that way.
CodePudding user response:
The issue can be reduced to
#include <algorithm>
static_assert(
std::copy_constructible<decltype(std::ranges::equal)>);
Since ranges::equal
requires Pred
must satisfy indirectly_comparable<I1, I2, Pred, Proj1, Proj2>
, which indirectly requires Pred
must satisfy copy_constructible
, so the constraints are not satisfied under MSVC. The standard doesn't require niebloid to be copyable, so both gcc and MSVC are correct.
The reason why MSVC's function objects are not copyable is that they all inherit _Not_quite_object
, which is implemented as
class _Not_quite_object {
_Not_quite_object(const _Not_quite_object&) = delete;
_Not_quite_object& operator=(const _Not_quite_object&) = delete;
};
As stated in its comments
Some overload sets in the library have the property that their constituent function templates are not visible to argument-dependent name lookup (ADL) and that they inhibit ADL when found via unqualified name lookup.
This property allows these overload sets to be implemented as function objects. We derive such function objects from this type to remove some typical object-ish behaviors which helps users avoid depending on their non-specified object-ness.
So you cannot pass ranges::equal
as a function object, because the standard doesn't require it to be.