Могу ли я развернуть итератор std ::map для структурированной привязки опций?

#c #c 17 #c 20 #structured-bindings

#c #c 17 #c 20 #структурированные привязки

Вопрос:

Рассмотрим следующий код:

 #include<functional>
#include<iostream>
#include<map>

const std::map<int, std::string> numberToStr{{1, "one"}, {2,"two"}};
int main() {
    auto it = numberToStr.find(2);
    if (it ==numberToStr.end()){
        return 1;
    }
    const autoamp;[_, str] = *it;
    std::cout << str;
}
 

Есть ли у меня какой-нибудь способ развернуть потенциально разыменованные it до 2 опций (_ и str), чтобы я мог затем написать:

 const autoamp;[_, str] = // some magic;
// _ is std::optional<int>, str is std::optional<str>
if (!str){
    return 1;
}
std::cout << *str;
}
 

Я полагаю, что нет, поскольку структурированные привязки — это вещь на уровне языка, а std ::optional — это функция библиотеки, и afaik нет способа настроить взаимодействие.

Примечание: Я предполагаю, что мог бы реализовать свою собственную карту, которая возвращает итераторы, которые знают, указывают ли они на .end() , и «взломать» точки настройки, чтобы выполнять необязательную логику, основанную на этом, я прошу общий вариант использования, когда я не контролирую контейнер.

Комментарии:

1. Я не понимаю. Если вы не управляете контейнером, вы получаете то, что получаете. Какую магию вы ожидаете? Нормально ли, если вызов .find обернут в функцию, которая возвращает пару опций?

2. @cigien Я наткнулся на стену, если бы я знал какое-то направление к решению, я бы его предоставил, но я знаю только то, что хочу (распаковка, возможно, пара. найти возвращает в пару необязательных-ов), Натан, ответ: я думаю, что лучшее, что я могу получить, не используя WG21, запекающий подобные вещи в язык…

3. Конечно, это нормально. Ваша формулировка немного сбила меня с толку, вот и все. Кстати, в любом случае это будет не проблема языка, а проблема библиотеки, поскольку она касается того, что map::find возвращается. Так что, я думаю, это будет зависеть от LWG. Не то, чтобы подпись когда-либо была изменена, чтобы делать то, что вы хотите.

4. Этот вопрос не имеет смысла для меня. Код уже проверяет результат find() , чтобы убедиться, что возвращаемый iterator является действительным, и в этом случае string для этого элемента map также действителен и не может быть необязательным. Нет необходимости использовать структурированную привязку вообще, когда вы можете просто получить доступ string непосредственно из итератора, например: if (it == numberToStr.end()){ return 1; } std::cout << it->second;

5. @cigien: Нет проблем: есть веб -сайт, который расскажет вам об этом, но вы должны знать, что он существует!

Ответ №1:

Вы могли бы добавить вспомогательную функцию, например

 template <typename Key, typename Value, typename... Rest>
std::pair<std::optional<Key>, std::optional<Value>> my_find(const std::map<Key, Value, Rest...>amp; map, const Keyamp; to_find)
{
    auto it = map.find(to_find);
    if (it == map.end())
        return {};
    else
        return {it->first, it->second};
}
 

и тогда вы бы использовали его как

 const autoamp;[_, str] = my_find(numberToStr, 2);
// _ is std::optional<int>, str is std::optional<str>
if (!str){
    return 1;
}
std::cout << *str;
 

Если вас интересует только значение, вы можете немного сократить код, просто вернув его вместо

 template <typename Key, typename Value, typename... Rest>
std::optional<Value> my_find(const std::map<Key, Value, Rest...>amp; map, const Keyamp; to_find)
{
    auto it = map.find(to_find);
    if (it == map.end())
        return {};
    else
        return {it->second};
}
 

и тогда вы бы использовали его как

 auto str = my_find(numberToStr, 2);
// str is std::optional<str>
if (!str){
    return 1;
}
std::cout << *str;
 

Комментарии:

1. Да, вероятно, то, что хочет OP, хороший ответ 🙂 Я не должен был ждать подтверждения комментария:(

2. Это то, о чем просил OP, но я думаю, что их лучше обслуживать std::optional<Value> my_at(const std::map<Key, Value, Rest...>amp; map, const Keyamp; to_find)

3. @Caleth хороший момент. добавил это в ответ.

4. В чем смысл find такой упаковки?

5. @NoSenseEtAl optional<reference_wrapper<T>> — это длинный способ написания T*

Ответ №2:

Более идиоматичным маршрутом C 20 было бы моделировать итератор как возможно пустой диапазон:

 auto const rng = std::apply(
    [](auto it, auto end) { return std::ranges::subrange(it, end); },
    numberToStr.equal_range(2));
if (rng.empty())
    return 1;
auto constamp; [_, str] = *rng.begin();
std::cout << str;
 

Пример.

Вы можете сделать это до C 20 с помощью Boost.Диапазоны, которые имеют более эргономичный iterator_range :

 auto const rng = boost::make_iterator_range(numberToStr.equal_range(2));
// ditto
 

Комментарии:

1. Не могли бы вы объяснить, почему проверка пустого диапазона лучше / более идиоматична, чем проверка того, указывает ли итератор на конец? Разве это не то, что было бы пустым диапазоном?

2. @Cedric это позволяет писать четко корректный код, используя for цикл для защиты доступа к (возможно, пустому) диапазону. См open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1255r6.html

3. @Cedric для меня диапазон менее идиоматичен, но, возможно, это просто привыкание к нему, я помню, что мне не понравился boost::optional, когда я увидел его в первый раз…

Ответ №3:

Желаемый API имеет для меня мало смысла. Зачем вам возвращать две опции? Ключ либо находится на карте, либо нет, это единственное измерение необязательности — это не значит, что вы можете вернуть задействованный ключ, но отключенное значение или отключенный ключ, но задействованное значение.

API должен быть:

 template <typename Map, typename Key>
auto try_find(Mapamp;, Keyamp;amp;) -> optional<range_reference_t<Map>>;
 

Но на самом деле мы не можем написать это с помощью std::optional , потому что он не поддерживает необязательные ссылки. Возврат фактического optional<value_type> значения является расточительным (дополнительные копии) и, вероятно, семантически недопустимым (вы, вероятно, хотели это конкретное значение, а не просто значение).

Итак, первым шагом является получение лучшей реализации optional и ее использование. В этот момент реализация здесь очень проста:

 template <typename Map, typename Key>
auto try_find(Mapamp; m, Keyamp;amp; k) -> optional<range_reference_t<Map>>
{
    auto it = m.find(std::forward<Key>(k));
    if (it != m.end()) {
        return *it;
    } else {
        return nullopt;
    }
}
 

Другой подход, который действительно работает std::optional , заключается в возврате необязательного итератора вместо необязательной ссылки. Преимущество этого заключается в том, что оно так же легко комбинируется, как optional и при работе полностью в стандартной библиотеке.


Третий подход заключается в том, чтобы вместо этого возвращать диапазон:

 template <typename Map, typename Key>
auto try_find(Mapamp; m, Key constamp; k) -> subrange<iterator_t<Map>>
{
    auto [f, l] = m.equal_range(key);
    return subrange(f, l);
}
 

Это продолжает быть составным со всеми диапазонами. Вы просто проверяете пустоту вместо вовлеченности:

 auto r = try_find(m, key);
if (r.empty()) {
    // nope
} else {
    // use r.front()
}
 

Комментарии:

1. Ну, это личное предпочтение, я предпочитаю, чтобы я мог проверять только str и использовать его немедленно, без необходимости сначала проверять, что пара в порядке, а затем get .second из него. У меня было долгое обсуждение других комментариев, это мое личное предпочтение, трудно заставить кого-то согласиться. 🙂 То же самое с необязательным итератором, мне не нравится синтаксис ** (или эквивалентный). Все еще поддержал ответ, поскольку я понятия не имел о optional<range_reference_t<Map>>

2. std::optional<std::reference_wrapper<ValueType>> также является опцией (sic).

3. @ecatmur Но это не то, что вы хотите сделать, это просто попытка обойти недостаток библиотеки. range_reference_t<M> не обязательно должен быть фактический ссылочный тип, возможно, у меня есть карта, которая выдает прокси-серверы — я бы хотел сохранить это. Другая проблема в том, что это не optional<reference_wrapper<range_value_t<M>> так — это больше похоже optional<reference_wrapper<remove_reference_t<range_reference_t<M>>>> . Но тогда это действительно, даже если у нас есть прокси, и мы безоговорочно возвращаем оборванные ссылки. Это какой-то беспорядок.

4. Ну, это optional<conditional_t<is_reference_v<range_reference_t<M>>, reference_wrapper<remove_reference_t<range_reference_t<M>>>, range_reference_t<M>>> потом. Жаль, что у нас есть unwrap_reference , но не обратная операция.