Implementación del operador in en C ++

Hola Hoy espero mostrarte algo de magia. Mi pasatiempo es inventar todo tipo de piezas aparentemente imposibles en C ++, lo que me ayuda a aprender todo tipo de sutilezas del lenguaje, o simplemente divertirme. El operador in está en varios idiomas, por ejemplo Python, JS. Pero no lo trajeron en C ++, pero a veces quiero que lo sea, así que ¿por qué no implementarlo?

std::unordered_map<std::string, std::string> some_map = { { "black", "white" }, { "cat", "dog" }, { "day", "night" } }; if (auto res = "cat" in some_map) { res->second = "fish"; } 


Cómo debería funcionar el operador, creo que es obvio. Toma el objeto izquierdo y comprueba si se produce este objeto en el objeto indicado a la derecha, que no tiene que ser una colección. No existe una solución universal en sí misma, al igual que no existe una solución universal para otros operadores, por lo tanto, se inventó la posibilidad de sobrecargarlos. Por lo tanto, para el operador in, debe implementar un mecanismo similar.

La sobrecarga se verá así.

 bool operator_in(const string& key, const unordered_map<string, string>& data) { return data.find(key) != data.end(); } 

Creo que la idea es clara, la expresión de la especie.

  "some string" in some_map 

Debería convertirse en una llamada de función.

  operator_in("some string", some_map) 

La implementación de este mecanismo es bastante simple, utilizando las capacidades existentes para la sobrecarga del operador. El operador in es esencialmente una macro que multiplica.

  #define in *OP_IN_HELP{}* 

En este caso, OP_IN_HELP es una clase vacía y solo nos sirve para seleccionar la sobrecarga correcta.

  class OP_IN_HELP {}; template<class TIn> OP_IN_LVAL<TIn> operator*(const TIn& data, const OP_IN_HELP&) { return OP_IN_LVAL<TIn>(data); } 

El operador es repetitivo, lo que le permite aceptar cualquier tipo como primer argumento. Ahora necesitamos obtener de alguna manera el objeto correcto, sin perder el izquierdo. Para hacer esto, implementamos la clase OP_IN_LVAL que almacenará nuestro objeto izquierdo.

  template<class TIn> struct OP_IN_LVAL { const TIn& m_in; OP_IN_LVAL(const TIn& val) : m_in(val) {}; }; 

Dado que el objeto en sí estará vivo mientras se ejecuta la expresión, no hay nada de qué preocuparse si mantenemos una referencia constante a este objeto. Ahora todo lo que nos queda es implementar el operador interno de multiplicación, que nos devolverá el resultado del operador sobrecargado, será una plantilla en sí misma.

  template<class TIn> struct OP_IN_LVAL { const TIn& m_in; OP_IN_LVAL(const TIn& val) : m_in(val) {}; template<class TWhat> bool operator*(const TWhat& what) const { return operator_in(m_in, what); } }; 

En realidad, esta solución ya funcionará, pero es limitada y no nos permitirá escribir de esta manera.

  if (auto res = "true" in some_map) { res->second = "false"; } 

Para que tengamos esa oportunidad, necesitamos arrojar el valor de retorno del operador sobrecargado. Hay dos versiones de cómo hacer esto, una usa las capacidades de C ++ 14, la otra funciona dentro de C ++ 11.

  template<class TIn> struct OP_IN_LVAL { const TIn& m_in; OP_IN_LVAL(const TIn& val) :m_in(val) {}; //   C++14 template<class TWhat> auto operator*(const TWhat& what) const { return operator_in(m_in, what); } //   C++11 template<class TWhat> auto operator*(const TWhat& what) const -> decltype(operator_in(m_in, what)) { return operator_in(m_in, what); } //       //       template<class TWhat> auto operator*(TWhat& what) const -> decltype(operator_in(m_in, what)) { return operator_in(m_in, what); } }; 

Como trabajo principalmente en Visual Studio 2013, estoy limitado al marco de trabajo de C ++ 11 y la solución dentro de C ++ 11 funcionará con éxito en C ++ 14, por lo que le aconsejo que lo elija.

Un ejemplo de implementación del operador genérico in para unordered_map.

 template<class TIterator> 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<class TKey, class TVal> auto operator_in(const TKey& key, std::unordered_map<TKey, TVal>& data) -> OpInResult<typename std::unordered_map<TKey, TVal>::iterator> { auto iter = data.find(key); return OpInResult<typename std::unordered_map<TKey, TVal>::iterator>(iter != data.end(), iter); } template<class TKey, class TVal> auto operator_in(const char* key, std::unordered_map<TKey, TVal>& data) -> OpInResult<typename std::unordered_map<TKey, TVal>::iterator> { auto iter = data.find(key); return OpInResult<typename std::unordered_map<TKey, TVal>::iterator>(iter != data.end(), iter); } 

La clase OpInResult le permite anular el operador de conversión , lo que nos permite usarlo en if. También anula el operador de flecha, que le permite enmascararse como un iterador que devuelve unordered_map.find ().

Un ejemplo se puede encontrar aquí cpp.sh/7rfdw

También me gustaría decir acerca de algunas características de esta solución.
Visual Studio crea una instancia de la plantilla en el lugar de uso, lo que significa que la función de sobrecarga debe declararse antes de usar el operador, pero puede declararse después de la declaración de clase OP_IN_LVAL . A su vez, GCC crea una instancia de la plantilla en la ubicación de la declaración (cuando encuentra uso por sí misma), lo que significa que una declaración sobrecargada debe declararse antes de declarar la clase OP_IN_LVAL . Si no está del todo claro de qué se trata, entonces aquí hay un ejemplo. cpp.sh/5jxcq En este código, acabo de sobrecargar el operador in debajo de la declaración de clase OP_IN_LVAL y dejó de compilar en GCC (a menos que compilara con el indicador -fpermissive), pero se compila correctamente en Visual Studio.

En C ++ 17, se hizo posible escribir así.

  if (auto res = some_map.find("true"); res != some_map.end()) { res->second = "false"; } 

Pero me parece el diseño de la vista.

  if (auto res = "true" in some_map) { res->second = "false"; } 

Se ve mejor.

Se pueden ver más ejemplos de sobrecargas aquí github.com/ChaosOptima/operator_in

Basado en el principio de implementación de este operador, tampoco habrá ningún problema para implementar
y otros operadores y expresiones, por ejemplo.

  negative = FROM some_vector WHERE [](int x){return x < 0;}; 

PS
Me gustaría saber si está interesado en estos temas, ¿hay algún punto al escribir sobre esto aquí? ¿Y le gustaría saber cómo implementar otras cosas interesantes?

operador condicional nulo

  auto result = $if some_ptr $->func1()$->func2()$->func3(10, 11, 0)$endif; 

emparejamiento patern

  succes = patern_match val with_type(int x) { cout << "some int " << x << '\n'; } with_cast(const std::vector<int>& items) { for (auto&& val : items) std::cout << val << ' '; std::cout << '\n'; } with(std::string()) [&](const std::string&) { cout << "empty string\n"; } with(oneof<std::string>("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"; }; 

cadena de enumeración

  StringEnum Power $def ( POW0, POW1, POW2 = POW1 * 2, POW3, POW4 = POW3 + 1, POW8 = POW4 * 2, POW9, POW10 ); to_string(Power::POW0) from_string<Power>("POW0") 

Source: https://habr.com/ru/post/es419579/


All Articles