Hans Dembinski
TU Dortmund
Example case: write a find
function that works with many containers
#include <iterator> // std::begin, std::end
#include <algorithm> // std::find
#include <set> // needed later
template <class Container, class Value>
auto find(Container const& c, Value const& v) {
// ADL-friendly use of begin, end
using std::begin; using std::end;
return std::find(begin(c), end(c), v);
}
Like std::find
, but we pass container and not pair of iterators
Passing the container itself allows us to enable optimisations
Explicit includes will be omitted in the following
std::set
, can do better by calling std::set::find
// simple find, has O(N) complexity
template <class Container, class Value>
auto find(Container const& c, Value const& v) {
// ADL-friendly use of begin, end
using std::begin; using std::end;
return std::find(begin(c), end(c), v);
}
// specialization for std::set, has O(logN) complexity
template <class K, class C, class A>
auto find(std::set<K, C, A> const& s, V const& v) {
return s.find(v);
}
std::unordered_set
and any other class which has a compatible interface, e.g. boost::set
, absl::btree_set
…std::set
-like container, need to detect find
method and use itdecltype
template <class Container, class Value>
auto find(Container const& c, Value const& v) {
// ADL-friendly use of begin, end
using std::begin; using std::end;
return std::find(begin(c), end(c), v);
}
// implementation for set-like, delegates to `find` method, if it exists
template <class Set, class Value>
auto find(Set const& s, Value const& v) -> decltype(s.find(v)) {
return s.find(v);
}
find
methodstd::enable_if
needed, no detection framework neededstd::vector
, but fails for std::set<int>
error: call of overloaded 'find(std::set<int> const&, int)' is ambiguous
std::set<int>
:return_type_1 find(std::set<int> const&, int const&); // from first template
return_type_2 find(std::set<int> const&, int const&); // from second template
template <class V> auto find(std::vector<int, std::allocator<T>> const& c, V const&);
template <class T, class V> auto find(std::vector<T, std::allocator<T>> const& c, V const&);
template <class T, class A, class V> auto find(std::vector<T, A> const&, V const&);
template <class C, class V> auto find(C const&, V const&);
float
-> int
)// helper class to resolve ambiguity by hand
template <int N>
struct priority : priority<N-1> {}; // derived class
template <>
struct priority<0> {}; // base class
template <class Container, class Value>
auto find_impl(Container const& c, Value const& v, priority<0>) {
using std::begin; using std::end;
return std::find(begin(c), end(c), v); // delegate to stdlib
}
template <class Container, class Value>
auto find_impl(Container const& c, Value const& v, priority<1>) -> decltype(c.find(v)) {
return c.find(v); // use that find method if it exists
}
template <class Container, class Value>
auto find(Container const& c, Value const& v) {
return find_impl(c, v, priority<1>{}); // calls find_impl with highest priority that passes substitution test
}
priority
helper type to resolve such ambiguities in an elegant and efficient waystd::enable_if
or detection frameworkfind_impl
, priority
never appears in compiled code