【问题标题】:Can I unwrap std::map iterator to structured binding of optionals?我可以将 std::map 迭代器解包到可选的结构化绑定吗?
【发布时间】:2020-12-03 16:28:04
【问题描述】:

考虑以下代码:

#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 auto&[_, str] = *it;
    std::cout << str;
}

我有什么办法可以将可能被取消引用的it 解包为 2 个选项(_ 和 str),这样我就可以写了:

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

我认为不是,因为结构化绑定是语言级别的东西,而 std::optional 是一个库功能,而且 afaik 没有办法自定义交互。

注意:我想我可以实现自己的映射,该映射返回知道它们是否指向 .end() 的迭代器,并“破解”自定义点以基于此执行可选逻辑,当我要求一般用例时不要控制容器。

【问题讨论】:

  • 我不关注。如果你不控制容器,你就会得到你所得到的。你期待什么样的魔法?如果对.find 的调用被包装在一个返回一对可选项的函数中,那可以吗?
  • @cigien 我碰壁了,如果我知道解决方案的一些方向,我会提供它,但我只知道我想要什么(解包可能是 .find 返回的一对可选-s) ,内森的回答是,我认为如果没有 WG21 将这样的东西烘焙成语言,我能得到最好的结果......
  • 当然,没关系。你的措辞让我有点失望,仅此而已。顺便说一句,这无论如何都不是语言问题,而是库问题,因为它处理 map::find 返回的内容。所以这将取决于 LWG,我认为。并不是说签名会被改变来做你想做的事。
  • 这个问题对我来说没有意义。代码已经在检查find() 的结果以确保返回的iterator 是有效的,在这种情况下,该地图元素的string 也是有效的并且不能是可选的。当您可以直接从迭代器访问 string 时,根本不需要使用结构化绑定,例如:if (it == numberToStr.end()){ return 1; } std::cout &lt;&lt; it-&gt;second;
  • @cigien:没问题:有website 告诉你这些事情,但你必须知道它的存在!

标签: c++ c++17 c++20 structured-bindings


【解决方案1】:

您可以添加一个辅助函数,例如

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

然后你会像这样使用它

const auto&[_, 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...>& map, const Key& 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;

【讨论】:

  • 是的,可能是 OP 想要的,很好的答案 :) 我不应该等待评论的确认 :(
  • 这是 OP 要求的,但我认为std::optional&lt;Value&gt; my_at(const std::map&lt;Key, Value, Rest...&gt;&amp; map, const Key&amp; to_find) 会更好地为他们服务
  • @Caleth 好点。将其添加到答案中。
  • 这样包装find有什么意义?
  • @NoSenseEtAl optional&lt;reference_wrapper&lt;T&gt;&gt; 是一种冗长的拼写方式 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 const& [_, str] = *rng.begin();
std::cout << str;

Example.

您可以在 C++20 之前使用 Boost.Ranges 执行此操作,它具有更符合人体工程学的 iterator_range

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

【讨论】:

  • 您能否解释一下为什么检查空范围比检查迭代器是否指向末尾更好/更惯用?这不是一个空范围吗?
  • @Cedric 它允许使用for 循环编写清晰正确的代码来保护对(可能为空的)范围的访问。见open-std.org/jtc1/sc22/wg21/docs/papers/2020/p1255r6.html
  • @Cedric 对我来说 range 的东西不太惯用,但也许只是习惯了它,我记得当我第一次看到它时我不喜欢 boost::optional ......
【解决方案3】:

所需的 API 对我来说毫无意义。你为什么要取回 两个 选项?密钥要么在地图中,要么不在地图中,这是一个单一维度的可选性 - 并不是说​​您可以取回已使用的密钥而是未使用的值或未使用的键但是可使用的值。

API应该是:

template <typename Map, typename Key>
auto try_find(Map&, Key&&) -> optional<range_reference_t<Map>>;

但我们实际上不能使用std::optional 编写它,因为它不支持可选引用。返回一个实际的optional&lt;value_type&gt; 既浪费(额外的副本),也可能在语义上无效(您可能想要那个特定的值,而不仅仅是a值)。

所以第一步是获得optional 的更好实现并使用它。至此,这里的实现就很简单了:

template <typename Map, typename Key>
auto try_find(Map& m, Key&& 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(Map& m, Key const& 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()
}

【讨论】:

  • 嗯,这是个人喜好,我更喜欢只检查 str 并立即使用它,而无需先检查配对是否正常,然后从中获取 .second 。我对其他A cmets讨论了很长时间,这是我个人的喜好,很难强迫别人同意。 :) 与可选的迭代器相同,我不喜欢 ** (或等效的)语法。仍然赞成答案,因为我不知道 optional>
  • std::optional&lt;std::reference_wrapper&lt;ValueType&gt;&gt; 也是一个选项(原文如此)。
  • @ecatmur 但这不是你想要做的,它只是解决图书馆缺陷的一个黑客。 range_reference_t&lt;M&gt; 不必是实际的引用类型,也许我有一个产生代理的地图 - 我想保留它。另一个问题是它不是optional&lt;reference_wrapper&lt;range_value_t&lt;M&gt;&gt; - 它更像是optional&lt;reference_wrapper&lt;remove_reference_t&lt;range_reference_t&lt;M&gt;&gt;&gt;&gt;。但是即使我们有代理也是有效的,并且我们无条件地返回悬空引用。有点乱。
  • 嗯,那就是optional&lt;conditional_t&lt;is_reference_v&lt;range_reference_t&lt;M&gt;&gt;, reference_wrapper&lt;remove_reference_t&lt;range_reference_t&lt;M&gt;&gt;&gt;, range_reference_t&lt;M&gt;&gt;&gt;。很遗憾我们有unwrap_reference,但没有反向操作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-10
  • 1970-01-01
  • 2022-01-23
  • 1970-01-01
  • 2021-11-13
相关资源
最近更新 更多