Search code examples
c++c++17c++20structured-bindings

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


Consider following code:

#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;
}

Is there any way for me to do the unwrapping of potentially dereferenced it to 2 optionals(_ and str) so I can then write:

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

I presume not since structured bindings are language level thing, and std::optional is a library feature and afaik there is no way to customize the interaction.

Note: I presume I could implement my own map that returns iterators that know if they point to .end(), and "hack" customization points to do optional logic based on that, I am asking for general use case when I do not control the container.


Solution

  • You could add a helper function like

    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};
    }
    

    and then you would use it like

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

    If you only care about the value, you can shorten the code a bit by just returning it instead with

    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};
    }
    

    and then you'd use it like

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