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) {};
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/7rfdwEu 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_inCom 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;};
PSGostaria 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")