Подтвердить что ты не робот

Могу ли я юридически использовать структуру с перегруженным оператором() в качестве Сравнить для std:: upper_bound?

У меня есть такие структуры (типы, упрощенные для переноса точки), живущие в std::vector:

struct Region {
    int first;
    int count;
    struct Metadata region_metadata;
};

В векторе они упорядочиваются на first. Если вы добавите first и count, вы получите first следующего региона; поэтому в основном этот вектор структур описывает метаданные для смежных диапазонов чисел.

Теперь, учитывая целое число, я хочу посмотреть метаданные. Когда регионы отсортированы, я могу использовать std::upper_bound. Я реализовал его следующим образом:

struct Comp
{
    inline bool operator()(const Region &region, int index) const
    {
        return region.first < index;
    }

    inline bool operator()(int index, const Region &region) const
    {
        return index < region.first;
    }
};

Это работает при вызове std::upper_bound с:

auto iter = std::upper_bound(m_regions.begin(),
                             m_regions.end(),
                             index,
                             Comp());

Теперь это происходит, потому что upper_bound может внутренне выбирать перегрузку, которая соответствует его требованиям, так как она вызывает как Comp()(Region, int), так и Comp()(int, Region) (из-за чего не работает [](const Region &reg, int index){…}).

Я действительно придумал решение, отслеживая сообщения об ошибках при использовании лямбда, о которой я упоминал ранее. docs для std:: upper_bound на cppreference.com напишите о четвертом аргументе:

объект функции сравнения (т.е. объект, который удовлетворяет требования Compare), который возвращает true, если первый аргумент меньше второго.

Подпись функции сравнения должна быть эквивалентна следующее:

bool cmp(const Type1 &a, const Type2 &b);

Подписи не требуется const &, но объект функции не должны изменять переданные ему объекты. Типы Type1 и Type2должен быть таким, чтобы объект типа T мог быть неявно преобразован в как Type1 и Type2, а объектом типа ForwardIt может быть разыменовывается и затем неявно преобразуется в оба Type1 и Type2.

Тип Type1 должен быть таким, чтобы объект типа T мог быть неявно преобразованный в Type1. Тип Type2 должен быть таким, чтобы объект типа ForwardIt может быть разыменован, а затем неявно преобразован в Type2.

(cppreference исправлено, так как я разместил этот вопрос, спасибо @T.C.)

Здесь T является третьим аргументом std::upper_bound и ForwardIt является типом первых двух аргументов. В этой цитате не говорится о функциональном объекте, фактически являющемся структурой, которая перегружает свой operator() для покрытия "вперед" и "обратных" ситуаций.

Итак, в Rules-As-Written это законно, или это артефакт моей конкретной комбинации компилятора/стандартной библиотеки (g++ 5.3.1)?

Меня интересуют ответы, специфичные для С++ 14 или С++ 17.

Полный пример:

#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 &region, int index) const
    {
        return region.first < index;
    }

    inline bool operator()(int index, const Region &region) 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;
}
4b9b3361

Ответ 1

Теперь это работает, потому что upper_bound может самостоятельно выбирать перегрузки, которая соответствует его требованиям, поскольку она вызывает оба Comp()(Region, int) и Comp()(int, Region) (вот почему a [](const Region &reg, int index){…} не работает).

Нет, upper_bound вызывает только Comp вторую перегрузку. Это то, о чем ваша цитата (fixed-thanks @T.C.!): Первый аргумент компаратора всегда является третьим аргументом upper_bound. Параметры лямбда должны быть заменены.

Перегрузка operator() внутри компаратора для upper_bound/lower_bound по своей сути бессмысленна, так как только одна перегрузка будет когда-либо выбрана этими алгоритмами.

operator() должен быть перегружен, как вы показали при работе с equal_range, и это законно, так как внутренние детали компаратора (или любого другого функтора) не имеют отношения к библиотеке: You нужно только убедиться, что порядок строгий (т.е. правильная семантика) и перегрузки однозначны.