Reputation: 8001
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...
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
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));
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
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());
}
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