This gives you an infix *in*
operator:
namespace notstd {
namespace ca_helper {
template<template<class...>class, class, class...>
struct can_apply:std::false_type{};
template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;
template<template<class...>class Z, class...Ts>
struct can_apply<Z,void_t<Z<Ts...>>, Ts...>:std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply = ca_helper::can_apply<Z,void,Ts...>;
namespace find_helper {
template<class C, class T>
using dot_find_r = decltype(std::declval<C>().find(std::declval<T>()));
template<class C, class T>
using can_dot_find = can_apply< dot_find_r, C, T >;
template<class C, class T>
constexpr std::enable_if_t<can_dot_find<C&, T>{},bool>
find( C&& c, T&& t ) {
using std::end;
return c.find(std::forward<T>(t)) != end(c);
}
template<class C, class T>
constexpr std::enable_if_t<!can_dot_find<C&, T>{},bool>
find( C&& c, T&& t ) {
using std::begin; using std::end;
return std::find(begin(c), end(c), std::forward<T>(t)) != end(c);
}
template<class C, class T>
constexpr bool finder( C&& c, T&& t ) {
return find( std::forward<C>(c), std::forward<T>(t) );
}
}
template<class C, class T>
constexpr bool find( C&& c, T&& t ) {
return find_helper::finder( std::forward<C>(c), std::forward<T>(t) );
}
struct finder_t {
template<class C, class T>
constexpr bool operator()(C&& c, T&& t)const {
return find( std::forward<C>(c), std::forward<T>(t) );
}
constexpr finder_t() {}
};
constexpr finder_t finder{};
namespace named_operator {
template<class D>struct make_operator{make_operator(){}};
template<class T, char, class O> struct half_apply { T&& lhs; };
template<class Lhs, class Op>
half_apply<Lhs, '*', Op> operator*( Lhs&& lhs, make_operator<Op> ) {
return {std::forward<Lhs>(lhs)};
}
template<class Lhs, class Op, class Rhs>
auto operator*( half_apply<Lhs, '*', Op>&& lhs, Rhs&& rhs )
-> decltype( named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) ) )
{
return named_invoke( std::forward<Lhs>(lhs.lhs), Op{}, std::forward<Rhs>(rhs) );
}
}
namespace in_helper {
struct in_t:notstd::named_operator::make_operator<in_t> {};
template<class T, class C>
bool named_invoke( T&& t, in_t, C&& c ) {
return ::notstd::find(std::forward<C>(c), std::forward<T>(t));
}
}
in_helper::in_t in;
}
On a flat container, like a vector array or string, it is O(n).
On an associative sorted container, like a std::map
, std::set
, it is O(lg(n)).
On an unordered associated container, like std::unordered_set
, it is O(1).
Test code:
std::vector<int> v{1,2,3};
if (1 *in* v)
std::cout << "yes\n";
if (7 *in* v)
std::cout << "no\n";
std::map<std::string, std::string, std::less<>> m{
{"hello", "world"}
};
if ("hello" *in* m)
std::cout << "hello world\n";
Live example.
C++14, but mainly for enable_if_t
.
So what is going on here?
Well, can_apply
is a bit of code that lets me write can_dot_find
, which detects (at compile time) if container.find(x)
is a valid expression.
This lets me dispatch the searching code to use member-find if it exists. If it doesn't exist, a linear search using std::find
is used instead.
Which is a bit of a lie. If you define a free function find(c, t)
in the namespace of your container, it will use that rather than either of the above. But that is me being fancy (and it lets you extend 3rd party containers with *in*
support).
That ADL (argument dependent lookup) extensibity (the 3rd party extension ability) is why we have three different functions named find
, two in a helper namespace and one in notstd
. You are intended to call notstd::find
.
Next, we want a python-like in
, and what is more python like than an infix operator? To do this in C++ you need to wrap your operator name in other operators. I chose *
, so we get an infix *in*
named operator.
TL;DR
You do using notstd::in;
to import the named operator in
.
After that, t *in* c
first checks if find(t,c)
is valid. If not, it checks if c.find(t)
is valid. If that fails, it does a linear search of c
using std::begin
std::end
and std::find
.
This gives you very good performance on a wide variety of std
containers.
The only thing it doesn't support is
if (7 *in* {1,2,3})
as operators (other than =
) cannot deduce initializer lists I believe. You could get
if (7 *in* il(1,2,3))
to work.