#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.html3. @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
, но не обратная операция.