Implementação do operador in em C ++

Oi Hoje espero mostrar um pouco de mágica. Meu hobby é inventar todo tipo de peças aparentemente impossíveis em C ++, o que me ajuda a aprender todos os tipos de sutilezas da linguagem, bem, ou apenas me divertindo. O operador in está em vários idiomas, por exemplo, Python, JS. Mas eles não o trouxeram em C ++, mas às vezes eu quero que seja, então por que não implementá-lo?

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"; } 


Como o operador deve funcionar, acho óbvio. Ele pega o objeto esquerdo e verifica se há uma ocorrência desse objeto no objeto indicado à direita, que não precisa ser uma coleção. Não existe uma solução universal por si só, assim como não há uma solução universal para outros operadores, portanto, a possibilidade de sobrecarregá-los foi inventada. Portanto, para o operador in, você precisa implementar um mecanismo semelhante.

Sobrecarga será assim.

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

Eu acho que a ideia é clara, a expressão das espécies.

  "some string" in some_map 

Deve se transformar em uma chamada de função.

  operator_in("some string", some_map) 

A implementação desse mecanismo é bastante simples, usando os recursos existentes para sobrecarga do operador. O operador in em si é essencialmente uma macro que faz multiplicação.

  #define in *OP_IN_HELP{}* 

Nesse caso, OP_IN_HELP é uma classe vazia e serve apenas para selecionar a sobrecarga correta.

  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); } 

O operador é clichê, que permite aceitar qualquer tipo como o primeiro argumento. Agora precisamos, de alguma forma, pegar o objeto certo, sem perder o esquerdo. Para fazer isso, implementamos a classe OP_IN_LVAL que armazenará nosso objeto esquerdo.

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

Como o próprio objeto estará vivo enquanto a expressão estiver em execução, não há com o que se preocupar se mantivermos uma referência constante a esse objeto. Agora, tudo o que resta para nós é implementar o operador interno de multiplicação, que nos retornará o resultado do operador sobrecarregado, que será o modelo por si só.

  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); } }; 

Na verdade, essa solução já funcionará, mas é limitada e não nos permitirá escrever dessa maneira.

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

Para termos essa oportunidade, precisamos lançar o valor de retorno do operador sobrecarregado. Existem duas versões de como fazer isso, uma usa os recursos do C ++ 14 e a outra funciona no 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 trabalho principalmente no Visual Studio 2013, estou limitado à estrutura do C ++ 11 e a solução no C ++ 11 funcionará com êxito no C ++ 14, por isso aconselho que você a escolha.

Um exemplo de implementação do operador genérico 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); } 

A classe OpInResult permite substituir o operador de conversão , o que nos permite usá-lo em if. Ele também substitui o operador de seta, que permite mascarar-se como um iterador que retorna unordered_map.find ().

Um exemplo pode ser encontrado aqui cpp.sh/7rfdw

Eu também gostaria de dizer sobre alguns recursos desta solução.
O Visual Studio instancia o modelo no local de uso, o que significa que a própria função de sobrecarga deve ser declarada antes que o operador seja usado, mas pode ser declarada após a declaração da classe OP_IN_LVAL . O GCC, por sua vez, instancia o modelo no local da declaração (quando encontra o uso por si só), o que significa que uma instrução sobrecarregada deve ser declarada antes que a classe OP_IN_LVAL seja declarada . Se não estiver totalmente claro do que se trata, aqui está um exemplo. cpp.sh/5jxcq Nesse código, sobrecarreguei o operador in abaixo da declaração de classe OP_IN_LVAL e ele parou de compilar no GCC (a menos que tenha sido compilado com o sinalizador -fpermissive), mas é compilado com êxito no Visual Studio.

No C ++ 17, tornou-se possível escrever assim.

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

Mas parece-me o design da vista

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

Parece melhor.

Mais exemplos de sobrecargas podem ser vistos aqui github.com/ChaosOptima/operator_in

Com base no princípio de implementação deste operador, também não haverá problemas para implementar
e outros operadores e expressões, por exemplo.

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

PS
Gostaria de saber se você está interessado em tais tópicos, há algum ponto em escrever sobre isso aqui? E você gostaria de saber como implementar outras coisas interessantes?

operador condicional nulo

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

correspondência 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"; }; 

enum de string

  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/pt419579/


All Articles