Как найти по константному ключу указателя на карте с неконстантными ключами указателя

Следующий код C++ не компилируется, потому что он передает неконстантный указатель на функцию find(), которая ожидает константный указатель.

#include <map>

std::map<int*, double> mymap;

double myfind(const int * mykey)
{
    return mymap.find(mykey)->second;
}

Есть ли способ заставить поиск работать, не меняя тип карты или не делая переменную mykey неконстантной? Ведь функция find() не модифицирует указанный объект, а просто сравнивает указатели.


person bedrorom    schedule 08.02.2019    source источник
comment
Используйте const_cast   -  person BartekPL    schedule 08.02.2019
comment
@t.niese Зачем тебе это менять? Как это решит основную проблему?   -  person curiousguy    schedule 08.02.2019
comment
Спустя много лет недостатки интерфейсов STL все еще присутствуют; особенно набор ассоциативных контейнеров, отсутствие бинарного поиска...   -  person curiousguy    schedule 08.02.2019
comment
Почему бы вместо этого не изменить тип ключа?   -  person Matthieu Brucher    schedule 08.02.2019
comment
@curiousguy не с прозрачными компараторами С++ 14. Это разница между std::less<int *> и std::less<const int *>   -  person Caleth    schedule 08.02.2019
comment
@Caleth Вы указали мне правильное направление, упомянув прозрачные компараторы.   -  person bedrorom    schedule 11.02.2019


Ответы (4)


Ключ в карте семантически неизменен, все операции карты, которые разрешают прямой доступ к ключам, делают это путем const-квалификации типа ключа (например, value_type определяется как pair<const Key, T>).

Однако в случае типа ключа int* вы получите указатель const на неконстантный int (int*const), что не очень приятно (он по-прежнему работает, поскольку в качестве значения используется только значение указателя). key, но семантика неизменяемости размывается, что может привести к ошибкам).

Вместо того, чтобы отказываться от константности, просто измените map на map<const int*, double>.

Тогда он будет работать как для const int*, так и для int* ключей.

#include <map>

std::map<const int*, double> mymap;

double myfind(const int * mykey)
{
    return mymap.find(mykey)->second; // just works
}

double myfind(int * mykey)
{
    return mymap.find(mykey)->second; // also works
}
person rustyx    schedule 08.02.2019
comment
Семантическое значение ключа не может быть изменено произвольным образом в ассоциативном контейнере, потому что контейнер использует сравнение (соответственно, хэш) для упорядочения объектов (соответственно, сохранения их в массиве). Но семантическое значение ключа здесь — это значение указателя. Вы можете изменить int, не ломая контейнер. - person curiousguy; 08.02.2019

Попробуйте const_cast, который позволяет изменить постоянство (или изменчивость) переменной.

#include <map>

std::map<int*, double> mymap;

double myfind(const int * mykey)
{
    return mymap.find(const_cast<int*>(mykey))->second;
}
person BartekPL    schedule 08.02.2019
comment
Да, но const_cast обычно является признаком плохого интерфейса - person curiousguy; 08.02.2019
comment
Я не рекомендую какой-либо актерский состав, но это один из возможных вариантов;) - person BartekPL; 08.02.2019

Я думаю, что нашел решение, но для него требуются прозрачные компараторы С++ 14.

#include <map>
#include <iostream>

struct CompareIntPtrs
{
    using is_transparent = void; // enabling C++14 transparent comparators

    bool operator()(const int * l, const int * r) const
    {
        return l < r;
    }
};

std::map<int*, double, CompareIntPtrs> mymap;

double myfind(const int * key)
{
    return mymap.find(key)->second;
}

int main()
{
    int x {6};
    mymap[&x] = 66; // inserting to the map
    const int * px = &x; // creating a "const int *" variable

    std::cout << myfind(px) << std::endl; // using "const int *" for finding in map with "int*" keys
    std::cout << mymap.find(px)->second << std::endl; // we could even skip using myfind()
}

Отличную статью о прозрачных компараторах C++14 можно найти здесь. Честно говоря, добавив компаратор, немного изменился тип mymap, чего я изначально не хотел, но это лучшее решение, которое я смог найти.

Если C++14 недоступен, есть по крайней мере два зла, из которых мы можем выбирать. Первый — скопировать mymap в новый std::map<const int*, double> в myfind, что ужасно неэффективно. Второй — отбрасывание константы с помощью const_cast<int*>(mykey), который должен быть избегайте, если это возможно.

person bedrorom    schedule 11.02.2019

У вас может быть проблема с константной правильностью. const int * может быть не тем, что вы думаю, что это. Это указатель на константу целое число. Это не то же самое, что тип ключа вашей карты, который представляет собой указатель на (непостоянное) целое число. И ни один из них не совпадает с int * const, который является постоянным указателем на (непостоянное) целое число. Проблема не в том, является ли само значение ключа изменяемым или неизменным, а в том, являются ли вещи, на которые вы храните указатели, изменяемыми или неизменяемыми.

Например, это компилируется:

std::map<int *, double> mymap;
double myfind(int * const mykey) {
    return mymap.find(mykey)->second;
}

Как и это:

std::map<const int *, double> mymap;
double myfind(const int *mykey) {
    return mymap.find(mykey)->second;
}
double myfind2(const int * const mykey) {
    return mymap.find(mykey)->second;
}

Вы видите разницу? В вашем исходном коде компилятор совершенно правильно отмечает ошибку. Если ваша функция принимает const int *, то вы, по сути, обещаете не изменять int, на который указывает указатель, который я передаю. Но если вы используете такой указатель, как int * в ключе std::map, вы можете позволить кому-то изменить что int.

В этом конкретном случае мы знаем, что std::map::find() не будет назначаться аргументу указателя, но компилятор этого не делает, поэтому const_cast<> существует, как указано в других ответах.

person TypeIA    schedule 08.02.2019
comment
Я понимаю разницу между const int * и int * const. Но вы правильно заметили в последнем абзаце. Мы знаем, что std::map::find() не изменит указанное целое число, но компилятор этого не знает. И в этом вопросе я спросил, как сообщить об этом компилятору. - person bedrorom; 11.02.2019
comment
@bedrorom и ответ на него тоже в последнем абзаце: const_cast<> - person TypeIA; 11.02.2019
comment
Да, const_cast<> — это одна из нескольких возможностей. Но отбрасывание const — это нехорошо практика. Пользовательский компаратор (если он есть) может сделать то же самое, не нарушая рекомендаций. - person bedrorom; 11.02.2019
comment
Полностью согласен, что это не очень хорошая практика, но я думаю, что если вы не измените тип ключа map, в конце концов это то, что вы делаете, независимо от того, через какие обручи вы прыгаете, чтобы добраться туда. Суть моего ответа, который я считаю важным и обоснованным для читателей, заключается в том, что const int * семантически отличается от int *. К этому мне действительно нечего добавить или внести свой вклад; Я сожалею, что это не понравилось даунвотеру. - person TypeIA; 11.02.2019