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.

    	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

    	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")
    


    Also popular now: