nodakai
nodakai

Reputation: 8001

C++ STL: std::find with std::map

Python allows you to write if e in arr: ... and if key in dict: ... which is handy.

Can we do something similar with the latter using std::find() and std::map ? That will allow me to uniformly handle std::array and std::map with a single generic function, without explicitly switching to std::map::find().

But if overloading operator==() is the only way, I'd rather give up this idea...

Update: note I already have a sort of solution

By "overloading operator==()" I meant something like this:

template<typename K>
struct KF {
    K&& k;

    template <typename V>
    friend bool operator==(const typename std::pair<const K, V>& pair, const KF<K>& o) {
        return pair.first == o.k;
    }

};

template <typename K>
KF<K> keyFinder(K&& k) { return KF<K>{ std::forward<K>(k) }; }

int main() {
    std::set<int> s{ 1, 2, };
    cout << (std::find(s.begin(), s.end(), 1) == s.end()) << endl; // => 0
    cout << (std::find(s.begin(), s.end(), 3) == s.end()) << endl; // => 1

    std::map<int, int> m{ {1,10}, {2,20}, };
    cout << (std::find(m.begin(), m.end(), keyFinder(1)) == m.end()) << endl; // => 0
    cout << (std::find(m.begin(), m.end(), keyFinder(3)) == m.end()) << endl; // => 1
}

Things get more complicated when we deal with non-scalar K in an universal way (perfect forwarding etc. ?)

Upvotes: 3

Views: 11057

Answers (2)

Vittorio Romeo
Vittorio Romeo

Reputation: 93264

...why not write your own utility function?

template <typename TContainer, typename TValue>
bool contains(const TContainer& c, const TValue& x);

You can use overloading to match the containers:

template <typename TValue, std::size_t N>
bool contains(const std::array<TValue, N>& c, const TValue& x)
{
    return std::find(std::begin(c), std::end(c), x) != std::end(c);
}

template <typename TValue, typename... Ts>
bool contains(const std::map<Ts...>& c, const TValue& x)
{
    return c.find(x) != std::end(c);
}

Usage:

std::array<int, 2> a{1,2};
std::map<int, int> b{{1,2},{3,4}};

assert(contains(a, 1));
assert(!contains(a, 42));
assert(contains(b, 1));
assert(!contains(b, 42));

live example on wandbox


If you want to support additional containers in the future, it's a good idea to use SFINAE to check whether or not a particular expression is valid. This approach works well because it doesn't care about the type of the container, it only cares about what operations can be performed on it.

The detection idiom would likely make it very easy to check member availability through SFINAE (and its implementation is C++11 compatible).

I also wrote an article about checking expression validity in-situ with C++17, which could be an interesting read. Despite its title, it covers C++11, C++14 and C++17 techniques to check expression validity:

"checking expression validity in-place with C++17"

Upvotes: 6

W.F.
W.F.

Reputation: 13988

To answer your explicit question - no, std::find won't be able to work uniformly for std::map/std::unordered_map and std::array/std::vector as the former is a collection of key/value pairs and the latter is collection of values...

You may want to use std::find_if instead as it gives you a little bit more flexibility on defining equality condition e.g. like this (c++1z approach):

#include <array>
#include <map>
#include <string>
#include <algorithm>
#include <type_traits>

template <class T>
struct is_pair: std::false_type { };

template <class K, class V>
struct is_pair<std::pair<K,V>>: std::true_type { };

int main() {
    std::map<std::string, int> m {{"abc", 1}, {"cde", 2}, {"efg", 3}};
    std::array<int, 5> a{1, 2, 3, 4, 5};

    auto lambda = [](auto it) {
        if constexpr (is_pair<decltype(it)>::value) {
            return it.second == 3;
        } else {
            return it == 3;
        }
    };

    assert(std::find_if(a.begin(), a.end(), lambda) != a.end());
    assert(std::find_if(m.begin(), m.end(), lambda) != m.end());
}

[live demo]

Have in mind that this approach won't work as expected if you decide to search through collection like std::vector<std::pair<int, int>>.

Upvotes: 4

Related Questions