我可以将 std::map 迭代器解包到可选项的结构化绑定吗?

Can I unwrap std::map iterator to structured binding of optionals?

考虑以下代码:

#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 是一个库功能,据我所知,没有办法自定义交互。

注意:我想我可以实现自己的地图,returns 迭代器知道它们是否指向 .end(),并且“hack”定制点基于此执行可选逻辑,我问对于我不控制容器的一般用例。

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

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;

更符合 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

想要的 API 对我来说意义不大。为什么要取回 两个 选项?键要么在地图中,要么不在地图中,这是可选性的单一维度 - 它不像您可以取回已启用的键而是未启用的值或未启用的键但已启用的值。

API应该是:

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

但实际上我们不能使用 std::optional 来编写它,因为它不支持可选引用。返回实际的 optional<value_type> 既浪费(额外的副本)又可能在语义上无效(您可能想要 那个特定的 值,而不仅仅是 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 的方法是 return 一个可选的 迭代器 而不是一个可选的引用。这样做的好处是与 optional 一样可组合,同时仍然完全在标准库中工作。


第三种方法是 return 范围:

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()
}