I'm writing a contains()
utility function and have come up with this. My question is: Is there a nicer way to select the right function to handle the call?
template <class Container>
inline auto contains(Container const& c,
typename Container::key_type const& key, int) noexcept(
noexcept(c.end(), c.find(key))) ->
decltype(c.find(key), true)
{
return c.end() != c.find(key);
}
template <class Container>
inline auto contains(Container const& c,
typename Container::value_type const& key, long) noexcept(
noexcept(c.end(), ::std::find(c.begin(), c.end(), key))
)
{
auto const cend(c.cend());
return cend != ::std::find(c.cbegin(), cend, key);
}
template <class Container, typename T>
inline auto contains(Container const& c, T const& key) noexcept(
noexcept(contains(c, key, 0))
)
{
return contains(c, key, 0);
}
For the record, you could write:
#include "magic.h"
template <typename T, typename... Us>
using has_find = decltype(std::declval<T>().find(std::declval<Us>()...));
template <class Container, typename T>
auto contains(const Container& c, const T& key)
{
return static_if<detect<has_find, decltype(c), decltype(key)>{}>
(
[&] (auto& cont) { return cont.end() != cont.find(key); },
[&] (auto& cont) { return cont.end() != std::find(cont.begin(), cont.end(), key); }
)(c);
}
where magic.h
contains:
#include <type_traits>
template <bool> struct tag {};
template <typename T, typename F>
auto static_if(tag<true>, T t, F f) { return t; }
template <typename T, typename F>
auto static_if(tag<false>, T t, F f) { return f; }
template <bool B, typename T, typename F>
auto static_if(T t, F f) { return static_if(tag<B>{}, t, f); }
template <bool B, typename T>
auto static_if(T t) { return static_if(tag<B>{}, t, [](auto&&...){}); }
template <typename...>
using void_t = void;
template <typename AlwaysVoid, template <typename...> class Operation, typename... Args>
struct detect_impl : std::false_type {};
template <template <typename...> class Operation, typename... Args>
struct detect_impl<void_t<Operation<Args...>>, Operation, Args...> : std::true_type {};
template <template <typename...> class Operation, typename... Args>
using detect = detect_impl<void, Operation, Args...>;
DEMO
namespace details {
template<template<class...>class Z, class, class...Ts>
struct can_apply:std::false_type{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z,std::void_t<Z<Ts...>>,Ts...>:std::true_type{};
};
template<template<class...>class Z, class...Ts>
using can_apply=typename details::can_apply<Z,void,Ts...>::type;
this takes a template and arguments, and tells you if you can apply it.
template<class T, class...Args>
using dot_find_r = decltype(std::declval<T>().find(std::declval<Args>()...));
template<class T, class...Args>
constexpr can_apply<dot_find_r, T, Args...> can_dot_find{};
we now tag dispatch on myfind
:
template<class C>
using iterator = decltype( ::std::begin(std::declval<C>()) );
namespace details {
template<class Container, class Key>
iterator<Container const&> myfind(
std::false_type can_dot_find,
Container const& c,
Key const& key
)
noexcept(
noexcept( ::std::find(::std::begin(c), ::std::end(c), key) )
)
{
return ::std::find( ::std::begin(c), ::std::end(c), key );
}
template <class Container, class Key>
iterator<Container const&> myfind(
std::true_type can_dot_find,
Container const& c,
Key const& key
) noexcept(
noexcept( c.find(key) )
)
{
return c.find(key);
}
}
template<class Container, class Key>
iterator<Container const&> myfind(
Container const& c,
Key const& k
) noexcept (
details::myfind( can_dot_find<Container const&, Key const&>, c, k )
)
{
return details::myfind( can_dot_find<Container const&, Key const&>, c, k );
}
template<class Container, class Key>
bool contains(
Container const& c,
Key const& k
) noexcept (
noexcept( ::std::end(c), myfind( c, k ) )
)
{
return myfind(c, k) != ::std::end(c);
}
As a bonus, the above version works with raw C style arrays.
The next enhancement I'd do would be an auto-ADL std::begin
to make begin
extensions work in the non-dot_find
case.
My personal equivalent returns a std::optional<iterator>
of the appropriate type. This both provides a quick "is it there", and gives easy access to the iterator if not not there.
if (auto oit = search_for( container, key )) {
// use *oit here as the iterator to the element, guaranteed not to be `end`
}
or
if (search_for( container, key )) {
// key was there
}
but that is neither here nor there.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With