Hallo! Heute hoffe ich, Ihnen etwas Magie zu zeigen. Mein Hobby ist es, alle Arten von scheinbar unmöglichen Stücken in C ++ zu erfinden, was mir hilft, alle Arten von Feinheiten der Sprache zu lernen oder einfach nur Spaß zu haben. Der Operator in ist in mehreren Sprachen verfügbar, z. B. Python, JS. Aber sie haben es nicht in C ++ gebracht, aber manchmal möchte ich, dass es so ist. Warum also nicht implementieren?
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"; }
Wie der Bediener arbeiten soll, finde ich offensichtlich. Er nimmt das linke Objekt und prüft, ob dieses Objekt in dem rechts angegebenen Objekt vorkommt, bei dem es sich nicht um eine Sammlung handeln muss. Es gibt keine universelle Lösung für sich, so wie es keine universelle Lösung für andere Betreiber gibt, daher wurde die Möglichkeit einer Überlastung erfunden. Daher müssen Sie für den Operator in einen ähnlichen Mechanismus implementieren.
Überladung wird so aussehen.
bool operator_in(const string& key, const unordered_map<string, string>& data) { return data.find(key) != data.end(); }
Ich denke, die Idee ist klar, der Ausdruck der Art.
"some string" in some_map
Es sollte sich in einen Funktionsaufruf verwandeln.
operator_in("some string", some_map)
Die Implementierung dieses Mechanismus ist recht einfach und nutzt die vorhandenen Funktionen zur Überlastung des Bedieners. Der In-Operator selbst ist im Wesentlichen ein Makro, das die Multiplikation ausführt.
#define in *OP_IN_HELP{}*
In diesem Fall ist
OP_IN_HELP eine leere Klasse und dient nur zur Auswahl der richtigen Überladung.
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); }
Der Operator ist Boilerplate, mit dem Sie einen beliebigen Typ als erstes Argument akzeptieren können. Jetzt müssen wir irgendwie das richtige Objekt finden, ohne das linke zu verlieren. Dazu implementieren wir die Klasse
OP_IN_LVAL, in der unser linkes Objekt
gespeichert wird.
template<class TIn> struct OP_IN_LVAL { const TIn& m_in; OP_IN_LVAL(const TIn& val) : m_in(val) {}; };
Da das Objekt selbst am Leben bleibt, während der Ausdruck ausgeführt wird, besteht kein Grund zur Sorge, wenn wir einen konstanten Verweis auf dieses Objekt beibehalten. Jetzt müssen wir nur noch den internen Multiplikationsoperator implementieren, der uns das Ergebnis des überladenen Operators zurückgibt. Er wird eine Vorlage für sich sein.
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); } };
Eigentlich wird diese Lösung bereits funktionieren, aber sie ist begrenzt und erlaubt uns nicht, auf diese Weise zu schreiben.
if (auto res = "true" in some_map) { res->second = "false"; }
Damit wir eine solche Gelegenheit haben, müssen wir den Rückgabewert des überladenen Operators werfen. Es gibt zwei Versionen, eine verwendet die Funktionen von C ++ 14, die andere funktioniert als Teil von C ++ 11.
template<class TIn> struct OP_IN_LVAL { const TIn& m_in; OP_IN_LVAL(const TIn& val) :m_in(val) {};
Da ich hauptsächlich in Visual Studio 2013 arbeite, bin ich auf das Framework von C ++ 11 beschränkt und die Lösung in C ++ 11 funktioniert erfolgreich in C ++ 14, daher empfehle ich Ihnen, sie auszuwählen.
Eine Beispielimplementierung des Generators in in für 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); }
Mit der
OpInResult- Klasse können
Sie den Cast-Operator überschreiben,
sodass wir ihn in if verwenden können. Außerdem wird der Pfeiloperator überschrieben, mit dem Sie sich als Iterator maskieren können, der unordered_map.find () zurückgibt.
Ein Beispiel finden Sie hier
cpp.sh/7rfdwIch möchte auch einige Funktionen dieser Lösung erläutern.
Visual Studio instanziiert die Vorlage am Verwendungsort.
Dies bedeutet, dass die Überladungsfunktion selbst deklariert werden muss, bevor der Operator verwendet wird, aber nach der
Deklaration der Klasse
OP_IN_LVAL deklariert werden kann. GCC wiederum instanziiert die Vorlage am Deklarationsspeicherort (wenn sie auf die Verwendung durch sich selbst stößt).
Dies bedeutet, dass eine überladene Anweisung deklariert werden muss, bevor die Klasse
OP_IN_LVAL deklariert wird . Wenn nicht ganz klar ist, worum es geht, finden Sie hier ein Beispiel.
cpp.sh/5jxcq In diesem Code habe ich gerade den Operator in unter der
Klassendeklaration OP_IN_LVAL überladen und die Kompilierung in GCC gestoppt (es sei denn, er wurde mit dem Flag -fpermissive kompiliert), aber in Visual Studio erfolgreich kompiliert.
In C ++ 17 wurde es möglich, so zu schreiben.
if (auto res = some_map.find("true"); res != some_map.end()) { res->second = "false"; }
Aber es scheint mir das Design der Ansicht
if (auto res = "true" in some_map) { res->second = "false"; }
Es sieht besser aus.
Weitere Beispiele für Überlastungen finden Sie hier
github.com/ChaosOptima/operator_inBasierend auf dem Prinzip der Implementierung dieses Operators wird es auch kein Problem geben, es zu implementieren
und andere Operatoren und Ausdrücke zum Beispiel.
negative = FROM some_vector WHERE [](int x){return x < 0;};
PSIch würde gerne wissen, ob Sie an solchen Themen interessiert sind. Gibt es einen Grund, hier darüber zu schreiben? Und möchten Sie wissen, wie Sie andere interessante Dinge umsetzen können?
nullbedingter 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<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"; };
Zeichenfolge enum
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")