Adding an 'in' operator to C++14
In languages such as python, there is an in
operator that is used to check if element is in a range:
if 5 in [1, 2, 3, 4, 5]: print("There is a 5")
What’s nice about this, is the almost english type readability of it. Let’s look at how we can implement such an operator in C++14.
No Macros
Using the infix adaptor in the Fit library, we can define named infix operators without having to resort to dangerous macros(such as #define in
). Here’s a simple example:
auto plus = infix([](int x, int y)
{
return x + y;
});
auto three = 1 <plus> 2;
Searching the range
Now, we can use the general purpose std::find
to search, however, associative containers such as map
or set
provide there own find
function that is either faster, or allows searching just by key. So let’s write a find_iterator
functions that will search by the member function find
if found, else it will search using std::find
:
FIT_STATIC_LAMBDA_FUNCTION(find_iterator) = fit::conditional(
[](const auto& r, const auto& x) -> decltype(r.find(x))
{
return r.find(x);
},
[](const auto& r, const auto& x)
{
using std::begin;
using std::end;
return std::find(begin(r), end(r), x);
}
);
The trailing decltype in the function (ie -> decltype(r.find(x))
) constraints the function such that if r.find
can’t be called the function won’t be called either(so the next function, ie the std::find
version, will be called instead).
There is one problem with this function. It doesn’t work with std::string
. As the black sheep of the family, the find
in std::string
returns an index instread of an iterator. So we can easily add another overload for std::string
that converts the index to an iterator:
FIT_STATIC_LAMBDA_FUNCTION(find_iterator) = fit::conditional(
[](const std::string& s, const auto& x)
{
auto index = s.find(x);
if (index == std::string::npos) return s.end();
else return s.begin() + index;
},
[](const auto& r, const auto& x) -> decltype(r.find(x))
{
return r.find(x);
},
[](const auto& r, const auto& x)
{
using std::begin;
using std::end;
return std::find(begin(r), end(r), x);
}
);
Putting it together
Well, now we can write the in
function, that calls find_iterator
and check for the end:
FIT_STATIC_LAMBDA_FUNCTION(in) = fit::infix(
[](const auto& x, const auto& r)
{
using std::end;
return find_iterator(r, x) != end(r);
}
);
So now we can use it for std::vector
:
std::vector<int> numbers = { 1, 2, 3, 4, 5 };
if (5 <in> numbers) std::cout << "Yes" << std::endl;
Or with an std::string
:
std::string s = "hello world";
if ("hello" <in> s) std::cout << "Yes" << std::endl;
Or even with an std::map
:
std::map<int, std::string> number_map = {
{ 1, "1" },
{ 2, "2" },
{ 3, "3" },
{ 4, "4" }
};
if (4 <in> number_map) std::cout << "Yes" << std::endl;
Now, when we want to negate the in
operator, we will need extra parenthesis because of operator precedence:
std::vector<int> numbers = { 1, 2, 3, 4, 5 };
if (not(8 <in> numbers)) std::cout << "No" << std::endl;