I have structs like this (types simplified to carry over the point), living in a std::vector:
struct Region {
int first;
int count;
struct Metadata region_metadata;
};
In the vector, they are ordered by first. If you add first and count, you get the first of the next region; so basically this vector of structs describes metadata for contiguous ranges of numbers.
Now given an integer, I want to look up the metadata. As the regions are sorted, I can use std::upper_bound. I implemented it this way:
struct Comp
{
inline bool operator()(const Region ®ion, int index) const
{
return region.first < index;
}
inline bool operator()(int index, const Region ®ion) const
{
return index < region.first;
}
};
This works, when calling std::upper_bound with:
auto iter = std::upper_bound(m_regions.begin(),
m_regions.end(),
index,
Comp());
Now this happens to work, because upper_bound can internally pick the overload which matches its requirements, as it calls both Comp()(Region, int) and Comp()(int, Region) (which is the reason why a [](const Region ®, int index){…} would not work).
I actually came up with the solution by tracing down the error messages when using the lambda I mentioned before. The docs for std::upper_bound at cppreference.com write about the fourth argument:
comparison function object (i.e. an object that satisfies the requirements of Compare) which returns true if the first argument is less than the second.
The signature of the comparison function should be equivalent to the following:
bool cmp(const Type1 &a, const Type2 &b);
The signature does not need to haveconst &, but the function object must not modify the objects passed to it. The typesType1andType2must be such that an object of typeTcan be implicitly converted to bothType1andType2, and an object of typeForwardItcan be dereferenced and then implicitly converted to bothType1andType2.The type
Type1must be such that an object of typeTcan be implicitly converted toType1. The typeType2must be such that an object of typeForwardItcan be dereferenced and then implicitly converted toType2.
(cppreference has been fixed since I posted this question, thanks @T.C.)
Here, T is the third argument to std::upper_bound and ForwardIt is the type of the first two arguments. This quote does not talk about the function object actually being a struct which overloads its operator() to cover the "forward" and "reverse" situations.
So in Rules-As-Written, is this legal, or is it an artifact of my specific compiler/standard library combination (g++ 5.3.1)?
I am interested in answers specific to C++14 or C++17.
Full example:
#include <algorithm>
#include <iostream>
#include <vector>
struct Region {
Region(int first, int count, int n):
first(first),
count(count),
n(n)
{
}
int first;
int count;
int n; // think struct Metadata
};
struct Comp
{
inline bool operator()(const Region ®ion, int index) const
{
return region.first < index;
}
inline bool operator()(int index, const Region ®ion) const
{
return index < region.first;
}
};
int main() {
std::vector<Region> regions;
regions.emplace_back(0, 10, 1);
regions.emplace_back(10, 10, 2);
regions.emplace_back(20, 10, 3);
const int lookup = 10;
auto iter = std::upper_bound(
regions.begin(),
regions.end(),
lookup,
Comp());
// yes, I omitted error checking here, with error being iter == regions.begin()
std::cout << lookup << " is in region with n = " << (iter-1)->n << std::endl;
}