Зачем мне добавлять «const» в пользовательский компаратор?

Я не очень хорошо знаком с C++ и не понимаю, как работает компаратор.

В приведенном ниже коде показано, как найти позицию, в которую я хочу вставить новый интервал:

#include <algorithm>
#include <vector> 

using namespace std;

int main()
{
    vector<vector<int>> intervals; // = ...
    vector<int> newInterval;       // = ...
    // ...

    auto comp = [](const vector<int>& a, const vector<int>& b) { return a[0] < b[0]; };
    auto it = upper_bound(intervals.begin(), intervals.end(), newInterval, comp);
}

Когда я попытаюсь удалить const, компиляция выдаст ошибку.

Зачем мне добавлять const для этого специального компаратора?

Поскольку компаратор не должен изменять то, что он сравнивает, это сделает сравнение невозможным.

Yksisarvinen 23.06.2024 17:35

Обратите внимание, что константную функцию-член можно вызывать как для неконстантного, так и для константного объекта. Но неконстантную функцию-член можно вызвать только для константного объекта. Хорошая книга по C++

user12002570 23.06.2024 17:39

Примечание: если вы изучаете C++, вам следует разучиться using namespace std;. См.: В чем проблема с «использованием пространства имен std;»?.

wohlstad 23.06.2024 17:39
std::upper_bound определено так, что const T& value принимается в качестве третьего параметра. И он передает это value компаратору. Таким образом, компаратор должен принять объект const хотя бы в качестве первого параметра.
Igor Tandetnik 23.06.2024 17:41
Предлагаемый дубликат рассказывает о том, почему operator() пользовательского компаратора должен быть const функцией-членом. Но это не то const, о котором спрашивается этот вопрос. (operator() лямбды по умолчанию равен const, если лямбда не отмечена mutable).
Igor Tandetnik 23.06.2024 17:47

Всегда публикуйте полную ошибку вместе с вашим вопросом. Кроме того, это не минимально воспроизводимый пример

user12002570 23.06.2024 17:49

@IgorTandetnik Еще один обман: Почему оператор вызова лямбды неявно является константой?

user12002570 23.06.2024 17:51
Демо, чтобы показать, что одного const в первом параметре достаточно для компиляции кода (не то чтобы это была хорошая идея). Это потому, что std::upper_bound всегда вызывает компаратор как comp(value, *it), где value — его третий параметр, а it — некоторый итератор в диапазоне; и value берется по константной ссылке.
Igor Tandetnik 23.06.2024 17:52

@ user12002570 ОП, кажется, спрашивает, почему параметры лямбды должны быть const; почему auto comp = [](vector<int>& a, vector<int>& b) { return a[0] < b[0]; }; не работает. По крайней мере, я так понял вопрос. Они говорят: «Я пытался удалить const», и это единственные случаи const, явно присутствующие в показанном коде.

Igor Tandetnik 23.06.2024 17:53

@IgorTandetnik интересно, что в документации указано comp, что «Хотя подпись не обязательно должна иметь const &, функция не должна изменять переданные ей объекты и должна иметь возможность принимать все значения типа (возможно, const) Type1 и Тип2 независимо от категории значения». Интересно, как этого можно добиться без const&?

wohlstad 23.06.2024 17:57

@Yksisarvinen, спасибо, я понял это (см. Мой ответ).

wohlstad 23.06.2024 18:17

Является ли мотивом этого вопроса тот факт, что во многих местах, если const X приемлемо (например, в качестве аргумента функции), то X тоже приемлемо?

Davis Herring 23.06.2024 18:36

Зачем мне добавлять «const» в пользовательский компаратор? Потому что уже слишком поздно менять язык, чтобы сделать const неявное значение по умолчанию и требовать mutable указывать неконстантный ссылочный параметр.

Eljay 24.06.2024 16:11

Конечно! Спасибо @wohlstad за четкий ответ :)

Yuan 27.07.2024 17:31

@Юань рад помочь. Кстати, на случай, если вы не в курсе: вы также можете проголосовать за ответ, голосование и принятие различны. См.: Что мне делать, если кто-то отвечает на мой вопрос?.

wohlstad 27.07.2024 20:51
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
15
171
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Из документации std::upper_bound относительно компаратора (comp):

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

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

Хотя подпись не обязательно должна содержать const &, функция должна не изменять передаваемые ему объекты и должен иметь возможность принимать все значения типа (возможно, const).
(таким образом, Type1 & не разрешен, как и Type1, если только для Type1 перемещение не эквивалентно копированию (начиная с C++11))

(выделено мной)

Поскольку компаратор должен иметь возможность принимать значения const (и не должен их изменять), тривиальным решением является использование const &. Другая альтернатива — принимать объекты по значению, что актуально только для простых типов, таких как int.

Примечание:

Как прокомментировал @IgorTandetnik на практике только первый объект должен быть const, во всех трёх основных компиляторах (gcc, clang, MSVC) - см. демо. Но это верно только в том случае, если используются неконстантные итераторы (как в опубликованном вами коде). Если вы используете константные итераторы (cbegin(), cend()), для компиляции оба объекта в компараторах должны быть const.

В любом случае хорошей практикой является наличие const & для обоих объектов в компараторе.

Причина, по которой второй параметр может быть неconst-ref, заключается в том, что передаются неконстантные итераторы. Если вы используете функции с префиксом c или делаете v константой, например std::upper_bound(v.cbegin(), v.cend(), 0, comp), код не скомпилируется.

Evg 23.06.2024 23:56

@Evg - хорошая мысль. Добавил уточнение в примечание.

wohlstad 24.06.2024 05:59

Другие вопросы по теме