Avatar ·

Ошибка "passing const xxx as 'this' argument discards qualifiers" в C++: как исправить

Привет! Я начинающий C++ разработчик, и я столкнулся с ошибкой, которую не могу понять. Пытаюсь использовать std::set для хранения пользовательских объектов, но компилятор выдаёт ошибку:
error: passing 'const MyClass' as 'this' argument of 'bool MyClass::operator<(const MyClass&)' discards qualifiers [-fpermissive]
Вот мой код:
#include <iostream>
#include <set>

class MyClass {
public:
    MyClass(int val) : value(val) {}

    bool operator<(const MyClass& other) {
        return this->value < other.value;
    }

    int getValue() const { return value; }

private:
    int value;
};

int main() {
    std::set<MyClass> mySet;
    mySet.insert(MyClass(10));
    mySet.insert(MyClass(5));
    
    for (const auto& elem : mySet) {
        std::cout << elem.getValue() << " ";
    }
    return 0;
}
Ошибка возникает в строке с определением оператора < . Я слышал что-то про "const correctness", но не понимаю, как это связано. Почему компилятор говорит, что я отбрасываю квалификаторы? И как это исправить? Помогите, пожалуйста, разобраться. Заранее спасибо! <<>>

Причина ошибки: const-корректность и работа std::set

Ошибка возникает из-за того, что std::set хранит элементы как константные объекты. Когда вы вставляете элемент в set, контейнер должен сравнивать его с существующими элементами, чтобы определить правильную позицию. Для этого он вызывает оператор сравнения, передавая константные ссылки на элементы. Однако ваш оператор operator< объявлен как неконстантный метод, то есть он может изменять объект, для которого вызывается (this не является константным). Компилятор запрещает вызывать неконстантные методы для константных объектов, отсюда и ошибка "discards qualifiers".

Решение: сделайте оператор сравнения константным

Исправление очень простое — добавьте ключевое слово const после объявления оператора:

bool operator<(const MyClass& other) const {
    return this->value < other.value;
}

Теперь метод обещает не изменять объект, для которого вызывается (this становится const MyClass*), и его можно безопасно вызывать для константных объектов.

Полный исправленный код

#include <iostream>
#include <set>

class MyClass {
public:
    MyClass(int val) : value(val) {}

    // Важно: добавляем const
    bool operator<(const MyClass& other) const {
        return this->value < other.value;
    }

    int getValue() const { return value; }

private:
    int value;
};

int main() {
    std::set<MyClass> mySet;
    mySet.insert(MyClass(10));
    mySet.insert(MyClass(5));
    
    for (const auto& elem : mySet) {
        std::cout << elem.getValue() << " ";
    }
    // Вывод: 5 10 (отсортировано)
    return 0;
}

Почему это важно? Правило const-корректности

В C++ действует важное правило: если метод не изменяет состояние объекта, он должен быть объявлен как const. Это:

  • Повышает безопасность — компилятор проверит, что вы случайно не измените объект в методе, который не должен этого делать.
  • Позволяет вызывать метод для константных объектов — как в нашем случае с std::set.
  • Улучшает читаемость кода — сразу видно, какие методы являются "наблюдателями" (не меняют объект), а какие — "модификаторами".

Дополнительные рекомендации

1. Всегда добавляйте const к операторам сравнения (operator<, operator== и т.д.), если они не должны изменять объект.

2. Для std::set и std::map оператор сравнения обязательно должен быть константным, иначе компиляция не удастся.

3. Используйте const для методов-геттеров — как вы уже сделали с getValue() const.

4. Если вы используете C++20, можно автоматически сгенерировать оператор сравнения с помощью auto operator<=>(const MyClass&) const = default; (космический корабль).

В итоге: ошибка "passing const xxx as 'this' argument discards qualifiers" почти всегда решается добавлением const к методу, который вызывается для константного объекта. В вашем случае — к оператору сравнения.

<<>>

Альтернативный подход: использование внешней функции сравнения

Вместо перегрузки оператора operator< внутри класса, вы можете определить внешнюю функцию сравнения и передать её в std::set как компаратор. Это может быть полезно, если вы не хотите изменять класс или если вам нужно несколько разных способов сравнения.

#include <iostream>
#include <set>

class MyClass {
public:
    MyClass(int val) : value(val) {}
    int getValue() const { return value; }
private:
    int value;
};

// Внешняя функция сравнения
struct CompareMyClass {
    bool operator()(const MyClass& a, const MyClass& b) const {
        return a.getValue() < b.getValue();
    }
};

int main() {
    // Передаём компаратор как второй шаблонный параметр
    std::set<MyClass, CompareMyClass> mySet;
    mySet.insert(MyClass(10));
    mySet.insert(MyClass(5));
    
    for (const auto& elem : mySet) {
        std::cout << elem.getValue() << " ";
    }
    // Вывод: 5 10
    return 0;
}

Когда это может быть полезно?

  • Если класс из сторонней библиотеки — вы не можете изменить его код, но можете определить свой компаратор.
  • Если нужно несколько критериев сортировки — создайте разные компараторы для разных контейнеров.
  • Если сравнение требует доступа к приватным данным — объявите компаратор как friend класса.

Этот подход полностью решает проблему с const-корректностью, так как внешняя функция принимает константные ссылки и сама объявлена как const. Однако он более многословен, чем добавление const к оператору внутри класса. Выбирайте тот способ, который лучше подходит для вашей ситуации.