Implementation of the in operator in C ++
Hello! Today I hope to show you some magic. My hobby is inventing all kinds of seemingly impossible pieces in C ++, which helps me in learning all kinds of subtleties of the language, or just to have fun. The in operator is in several languages, for example Python, JS. But in C ++ they didn’t deliver it, but sometimes I want it to be, so why not implement it.
How the operator should work, I think is obvious. He takes the left object and checks if there is an occurrence of this object in the object indicated on the right, which does not have to be a collection. There is no universal solution by itself, just as there is no universal solution for other operators, so the possibility of overloading them was invented. Therefore, for the in operator, you need to implement a similar mechanism.
Overload will look like this.
I think the idea is clear, the expression of the species.
It should turn into a function call.
Implementing this mechanism is quite simple, using the existing capabilities for operator overloading. The in operator itself is essentially a macro that does multiplication.
In this case, OP_IN_HELP is an empty class and serves us only to select the correct overload.
The operator is boilerplate, which allows you to accept any type as the first argument. Now we need to somehow get the right object, without losing the left. To do this, we implement the class OP_IN_LVAL which will store our left object.
Since the object itself will be alive while the expression is running, there is nothing to worry about if we keep a constant reference to this object. Now all that remains for us is to implement the internal operator of multiplication, which will return the result of the overloaded operator in to us; by itself, it will be template.
Actually this solution will already work, but it is limited and will not allow us to write this way.
In order for us to have such an opportunity, we need to throw the return value of the overloaded operator. There are two versions of how to do this, one uses the capabilities of C ++ 14, the other works within C ++ 11.
Since I mainly work in Visual Studio 2013, I am limited to the framework of C ++ 11 and the solution within C ++ 11 will work successfully in C ++ 14, so I advise you to choose it.
An example implementation of the generic in operator for unordered_map.
The OpInResult class allows you to override the cast operator, which allows us to use it in if. It also overrides the arrow operator, which allows you to mask yourself as an iterator that returns unordered_map.find ().
An example can be seen here cpp.sh/7rfdw
I would also like to say about some features of this solution.
Visual Studio instantiates the template at the place of use, which means that the overload function itself must be declared before the operator is used, but can be declared after the OP_IN_LVAL class declaration. GCC in turn instantiates the template at the declaration location (when it encounters use by itself), which means that an overloaded statement must be declared before the OP_IN_LVAL class is declared . If it’s not entirely clear what this is about, then here is an example. cpp.sh/5jxcq In this code, I just overloaded the in operator below the OP_IN_LVAL class declaration and it stopped compiling in GCC (unless it compiled with the -fpermissive flag), but it compiles successfully in Visual Studio.
In C ++ 17, it became possible to write like this.
But it seems to me the design of the view
It looks nicer.
More examples of overloads can be seen here github.com/ChaosOptima/operator_in
Based on the principle of implementing this operator, there will also be no problem to implement
other operators and expressions, for example.
std::unordered_map some_map =
{
{ "black", "white" },
{ "cat", "dog" },
{ "day", "night" }
};
if (auto res = "cat" in some_map)
{
res->second = "fish";
}
How the operator should work, I think is obvious. He takes the left object and checks if there is an occurrence of this object in the object indicated on the right, which does not have to be a collection. There is no universal solution by itself, just as there is no universal solution for other operators, so the possibility of overloading them was invented. Therefore, for the in operator, you need to implement a similar mechanism.
Overload will look like this.
bool operator_in(const string& key, const unordered_map& data)
{
return data.find(key) != data.end();
}
I think the idea is clear, the expression of the species.
"some string" in some_map
It should turn into a function call.
operator_in("some string", some_map)
Implementing this mechanism is quite simple, using the existing capabilities for operator overloading. The in operator itself is essentially a macro that does multiplication.
#define in *OP_IN_HELP{}*
In this case, OP_IN_HELP is an empty class and serves us only to select the correct overload.
class OP_IN_HELP
{};
template
OP_IN_LVAL operator*(const TIn& data, const OP_IN_HELP&)
{
return OP_IN_LVAL(data);
}
The operator is boilerplate, which allows you to accept any type as the first argument. Now we need to somehow get the right object, without losing the left. To do this, we implement the class OP_IN_LVAL which will store our left object.
template
struct OP_IN_LVAL
{
const TIn& m_in;
OP_IN_LVAL(const TIn& val) : m_in(val)
{};
};
Since the object itself will be alive while the expression is running, there is nothing to worry about if we keep a constant reference to this object. Now all that remains for us is to implement the internal operator of multiplication, which will return the result of the overloaded operator in to us; by itself, it will be template.
template
struct OP_IN_LVAL
{
const TIn& m_in;
OP_IN_LVAL(const TIn& val) : m_in(val)
{};
template
bool operator*(const TWhat& what) const
{
return operator_in(m_in, what);
}
};
Actually this solution will already work, but it is limited and will not allow us to write this way.
if (auto res = "true" in some_map)
{
res->second = "false";
}
In order for us to have such an opportunity, we need to throw the return value of the overloaded operator. There are two versions of how to do this, one uses the capabilities of C ++ 14, the other works within C ++ 11.
template
struct OP_IN_LVAL
{
const TIn& m_in;
OP_IN_LVAL(const TIn& val)
:m_in(val)
{};
// Версия для C++14
template
auto operator*(const TWhat& what) const
{
return operator_in(m_in, what);
}
// Версия для C++11
template
auto operator*(const TWhat& what) const -> decltype(operator_in(m_in, what))
{
return operator_in(m_in, what);
}
// Для мутабельных объектов нам нужен оператор
// принимающий объект по не константной ссылке
template
auto operator*(TWhat& what) const -> decltype(operator_in(m_in, what))
{
return operator_in(m_in, what);
}
};
Since I mainly work in Visual Studio 2013, I am limited to the framework of C ++ 11 and the solution within C ++ 11 will work successfully in C ++ 14, so I advise you to choose it.
An example implementation of the generic in operator for unordered_map.
template
class OpInResult
{
bool m_result;
TIterator m_iter;
public:
OpInResult(bool result, TIterator& iter)
: m_result(result), m_iter(iter)
{}
operator bool()
{
return m_result;
}
TIterator& operator->()
{
return m_iter;
}
TIterator& data()
{
return m_iter;
}
};
template
auto operator_in(const TKey& key, std::unordered_map& data) ->
OpInResult::iterator>
{
auto iter = data.find(key);
return OpInResult::iterator>(iter != data.end(), iter);
}
template
auto operator_in(const char* key, std::unordered_map& data) ->
OpInResult::iterator>
{
auto iter = data.find(key);
return OpInResult::iterator>(iter != data.end(), iter);
}
The OpInResult class allows you to override the cast operator, which allows us to use it in if. It also overrides the arrow operator, which allows you to mask yourself as an iterator that returns unordered_map.find ().
An example can be seen here cpp.sh/7rfdw
I would also like to say about some features of this solution.
Visual Studio instantiates the template at the place of use, which means that the overload function itself must be declared before the operator is used, but can be declared after the OP_IN_LVAL class declaration. GCC in turn instantiates the template at the declaration location (when it encounters use by itself), which means that an overloaded statement must be declared before the OP_IN_LVAL class is declared . If it’s not entirely clear what this is about, then here is an example. cpp.sh/5jxcq In this code, I just overloaded the in operator below the OP_IN_LVAL class declaration and it stopped compiling in GCC (unless it compiled with the -fpermissive flag), but it compiles successfully in Visual Studio.
In C ++ 17, it became possible to write like this.
if (auto res = some_map.find("true"); res != some_map.end())
{
res->second = "false";
}
But it seems to me the design of the view
if (auto res = "true" in some_map)
{
res->second = "false";
}
It looks nicer.
More examples of overloads can be seen here github.com/ChaosOptima/operator_in
Based on the principle of implementing this operator, there will also be no problem to implement
other operators and expressions, for example.
negative = FROM some_vector WHERE [](int x){return x < 0;};
PS
I would like to know if you are interested in such topics, is there any point in writing about this here? And would you like to know how to implement other interesting things?
null-conditional operator
patern matching
string enum
null-conditional operator
auto result = $if some_ptr $->func1()$->func2()$->func3(10, 11, 0)$endif;
patern matching
succes = patern_match val
with_type(int x)
{
cout << "some int " << x << '\n';
}
with_cast(const std::vector& items)
{
for (auto&& val : items)
std::cout << val << ' ';
std::cout << '\n';
}
with(std::string()) [&](const std::string&)
{
cout << "empty string\n";
}
with(oneof("one", "two", "three")) [&](const std::string& value)
{
cout << value << "\n";
}
with_cast(const std::string& str)
{
cout << "some str " << str << '\n';
}
at_default
{
cout << "no match";
};
string enum
StringEnum Power $def
(
POW0,
POW1,
POW2 = POW1 * 2,
POW3,
POW4 = POW3 + 1,
POW8 = POW4 * 2,
POW9,
POW10
);
to_string(Power::POW0)
from_string("POW0")