问题
In the following code, why do all three IntComparator()
, IntComparator2
and IntComparator3
work as the 3rd parameter of the sort()
function? Wouldn't they have different l-value function types? Based on https://en.cppreference.com/w/cpp/algorithm/sort it says
The signature of the comparison function should be equivalent to the following:
bool cmp(const Type1 &a, const Type2 &b);
which seems to match IntComparator2
better?
Also which one would be preferable? Third option seems much simpler and more intuitive.
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
struct IntComparator
{
bool operator()(const int &a, const int &b) const
{
return a < b;
}
};
bool IntComparator2 (const int &a, const int &b)
{
return a < b;
}
bool IntComparator3 (int a, int b)
{
return a < b;
}
int main()
{
int items[] = { 4, 3, 1, 2 };
std::sort(items, items+4, IntComparator());
for (int n=0; n<4; n++) {
std::cout << items[n] << ", ";
}
std::cout << "\n";
int items2[] = { 4, 3, 1, 2 };
std::sort(items2, items2+4, IntComparator2);
for (int n=0; n<4; n++) {
std::cout << items2[n] << ", ";
}
std::cout << "\n";
int items3[] = { 4, 3, 1, 2 };
std::sort(items3, items3+4, IntComparator3);
for (int n=0; n<4; n++) {
std::cout << items3[n] << ", ";
}
std::cout << "\n";
return 0;
}
回答1:
std::sort
accepts a functor
. This is any object that can be called (with the correct parameters). The function achieves this by using templates, like the following
template<typename Iter, typename Comp>
void sort(Iter begin, Iter end, Comp compare) { ... }
IntComparator1
, 2, and 3 are all valid functors for this comparator, since they can all be called using operator() with 2 integers.
Also like you said, the third option is indeed usually more intuitive.
回答2:
The sort()
function will just call the comparator function you provided when a comparison is needed. The way your comparator gets its parameters (value, reference, const ref) is not the concern of sort()
, it will call it the same way (passing two arguments of the same type) no matter how your comparator gets its parameters internally.
It is imperceptible from outside the comparator definition since the way we call you three functions is exactly the same.
The only thing that is requested is that the comparator takes only two arguments and they must be of the same type of the elements to be sorted.
But, passing by const ref is better because your comparator guarantees to not modify the paramaters it compares, and also that it avoids useless copies (performance gain). That is why they have written should be equivalent (which is different of must be equivalent).
回答3:
They are equivalent because the C++ specification says they both match the requirements of a binary predicate. The following extracts seem relevant.
[function.objects]
20.14.1 A function object type is an object type that can be the type of the postfix-expression in a function call ([expr.call], [over.match.call]).224 A function object is an object of a function object type. In the places where one would expect to pass a pointer to a function to an algorithmic template, the interface is specified to accept a function object. This not only makes algorithmic templates work with pointers to functions, but also enables them to work with arbitrary function objects.
[alg.sorting]
25.7.2 Compare is a function object type ([function.objects]) that meets the requirements for a template parameter named BinaryPredicate ([algorithms.requirements]). The return value of the function call operation applied to an object of type Compare, when contextually converted to bool ([conv]), yields true if the first argument of the call is less than the second, and false otherwise. Compare comp is used throughout for algorithms assuming an ordering relation.
[algorithms.requirements]
25.2.8 When not otherwise constrained, the BinaryPredicate parameter is used whenever an algorithm expects a function object that when applied to the result of dereferencing two corresponding iterators or to dereferencing an iterator and type T when T is part of the signature returns a value testable as true. In other words, if an algorithm takes BinaryPredicate binary_pred as its argument and first1 and first2 as its iterator arguments with respective value types T1 and T2, it should work correctly in the construct binary_pred(*first1, *first2) contextually converted to bool ([conv]). Unless otherwise specified, BinaryPredicate always takes the first iterator's value_type as its first argument, that is, in those cases when T value is part of the signature, it should work correctly in the construct binary_pred(*first1, value) contextually converted to bool ([conv]). binary_pred shall not apply any non-constant function through the dereferenced iterators. Given a glvalue u of type (possibly const) T1 that designates the same object as *first1, and a glvalue v of type (possibly const) T2 that designates the same object as *first2, binary_pred(u, *first2), binary_pred(*first1, v), and binary_pred(u, v) shall each be a valid expression that is equal to binary_pred(*first1, *first2), and binary_pred(u, value) shall be a valid expression that is equal to binary_pred(*first1, value).
As to the question on which is preferable, I'd say that is opinion based, unless in your specific case profiling shows one to be better.
来源:https://stackoverflow.com/questions/56183031/stdsort-with-custom-comparator