Implémentation de l'opérateur in en C ++

Salut Aujourd'hui, j'espère vous montrer de la magie. Mon hobby est d'inventer toutes sortes de pièces apparemment impossibles en C ++, ce qui m'aide à apprendre toutes sortes de subtilités du langage, ou tout simplement à m'amuser. L'opérateur in est en plusieurs langues, par exemple Python, JS. Mais ils ne l'ont pas apporté en C ++, mais parfois je le veux, alors pourquoi ne pas l'implémenter.

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


Je pense que le fonctionnement de l'opérateur devrait être évident. Il prend l'objet de gauche et vérifie s'il y a une occurrence de cet objet dans l'objet indiqué à droite, qui ne doit pas être une collection. Il n'y a pas de solution universelle en soi, tout comme il n'y a pas de solution universelle pour les autres opérateurs, par conséquent, la possibilité de les surcharger a été inventée. Par conséquent, pour l'opérateur in, vous devez implémenter un mécanisme similaire.

La surcharge ressemblera à ceci.

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

Je pense que l'idée est claire, l'expression de l'espèce.

  "some string" in some_map 

Il devrait se transformer en appel de fonction.

  operator_in("some string", some_map) 

La mise en œuvre de ce mécanisme est assez simple, en utilisant les capacités existantes de surcharge de l'opérateur. L'opérateur in lui-même est essentiellement une macro qui effectue la multiplication.

  #define in *OP_IN_HELP{}* 

Dans ce cas, OP_IN_HELP est une classe vide et nous sert uniquement à sélectionner la surcharge correcte.

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

L'opérateur est passe-partout, ce qui vous permet d'accepter n'importe quel type comme premier argument. Maintenant, nous devons en quelque sorte obtenir le bon objet, sans perdre celui de gauche. Pour ce faire, nous implémentons la classe OP_IN_LVAL qui stockera notre objet gauche.

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

Puisque l'objet lui-même sera vivant pendant que l'expression est en cours d'exécution, il n'y a rien à craindre si nous gardons une référence constante à cet objet. Maintenant il ne nous reste plus qu'à implémenter l'opérateur interne de multiplication, qui nous restituera le résultat de l'opérateur surchargé, il sera modèle par lui-même.

  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 fait, cette solution fonctionnera déjà, mais elle est limitée et ne nous permettra pas d'écrire de cette façon.

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

Pour que nous ayons une telle opportunité, nous devons lancer la valeur de retour de l'opérateur surchargé. Il existe deux versions de la façon de procéder, l'une utilise les capacités de C ++ 14, l'autre fonctionne dans le cadre 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); } }; 

Étant donné que je travaille principalement dans Visual Studio 2013, je suis limité au cadre de C ++ 11 et la solution au sein de C ++ 11 fonctionnera avec succès en C ++ 14, je vous conseille donc de le choisir.

Un exemple d'implémentation de l'opérateur générique dans pour 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 classe OpInResult vous permet de remplacer l'opérateur de transtypage , ce qui nous permet de l'utiliser dans if. Il remplace également l'opérateur flèche, qui vous permet de vous masquer comme un itérateur qui retourne unordered_map.find ().

Un exemple peut être trouvé ici cpp.sh/7rfdw

Je voudrais également parler de certaines fonctionnalités de cette solution.
Visual Studio instancie le modèle sur le lieu d'utilisation, ce qui signifie que la fonction de surcharge elle-même doit être déclarée avant que l'opérateur ne soit utilisé, mais peut être déclarée après la déclaration de classe OP_IN_LVAL . GCC à son tour instancie le modèle à l'emplacement de déclaration (lorsqu'il rencontre une utilisation par lui-même), ce qui signifie qu'une instruction surchargée doit être déclarée avant que la classe OP_IN_LVAL ne soit déclarée . Si vous ne savez pas exactement de quoi il s'agit, alors voici un exemple. cpp.sh/5jxcq Dans ce code, je viens de surcharger l'opérateur in sous la déclaration de classe OP_IN_LVAL et il a cessé de compiler dans GCC (à moins qu'il ne soit compilé avec l'indicateur -fpermissive), mais il se compile avec succès dans Visual Studio.

En C ++ 17, il est devenu possible d'écrire comme ça.

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

Mais il me semble que la conception de la vue

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

C'est plus joli.

Plus d'exemples de surcharges peuvent être vus ici github.com/ChaosOptima/operator_in

Sur la base du principe de mise en œuvre de cet opérateur, il n'y aura également aucun problème à mettre en œuvre
et d'autres opérateurs et expressions, par exemple.

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

PS
J'aimerais savoir si vous êtes intéressé par de tels sujets, est-il utile d'écrire à ce sujet ici? Et aimeriez-vous savoir comment mettre en œuvre d'autres choses intéressantes?

opérateur null-conditionnel

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

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

énumération de chaîne

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


All Articles