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 typesType1
andType2
must be such that an object of typeT
can be implicitly converted to bothType1
andType2
, and an object of typeForwardIt
can be dereferenced and then implicitly converted to bothType1
andType2
.The type
Type1
must be such that an object of typeT
can be implicitly converted toType1
. The typeType2
must be such that an object of typeForwardIt
can 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;
}