Einfach anfangen.
Single -Header -Bibliothek.
Makrofreie APIs.
Keine Haufenspeicherzuweisung.
Portabilität: kontinuierlich unter Ubuntu, MacOS und Windows mit GCC/Clang/MSVC getestet.
Keine externen Abhängigkeiten.
Zuverlässigkeit: Strict Compiler Checking -Option + Desinfektionsmittel + Valgrind.
Komponierbare Muster.
Erweiterbare Benutzer können ihre eigenen Muster definieren, entweder über das komponierende existierende oder brandneue.
Unterstützen Sie zerstörende tupelähnliche und rangeartige Behälter.
Teilweise Unterstützung für einen ständigen Ausdruck.
matchit.h Laden Sie einfach die Header -Datei matchit.h herunter und legen Sie sie in Ihr Include -Verzeichnis für Abhängigkeiten.
Das war's.
Sie können über diesen Bash -Befehl herunterladen
wget https://raw.githubusercontent.com/BowenFu/matchit.cpp/main/include/matchit.hFügen Sie den Code -Snippet in Ihre CMakelists ein.
include (FetchContent)
FetchContent_Declare(
matchit
GIT_REPOSITORY https://github.com/BowenFu/matchit.cpp.git
GIT_TAG main)
FetchContent_GetProperties(matchit)
if ( NOT matchit_POPULATED)
FetchContent_Populate(matchit)
add_subdirectory ( ${matchit_SOURCE_DIR} ${matchit_BINARY_DIR}
EXCLUDE_FROM_ALL )
endif ()
message ( STATUS "Matchit header are present at ${matchit_SOURCE_DIR} " ) Und fügen Sie ${matchit_SOURCE_DIR}/include zu Ihrem Include -Pfad hinzu.
Ersetzen Sie main -Release -Tag, um das Brechen der API -Kompatibilität zu vermeiden.
Klonen das Repo über
git clone --depth 1 https://github.com/BowenFu/matchit.cpp
Installieren Sie die Bibliothek über
cd matchit.cpp
cmake -B ./build
cd build
make install
Verwenden Sie dann Find_package in Ihren cmakelists.txt.
(Vielen Dank an @Daljit97 für das Hinzufügen der Unterstützung.)
vcpkg install matchit
Jetzt wurde die Bibliothek dem Conan Center Index eingereicht.
Sie können die Bibliothek jetzt über Conan installieren.
(Vielen Dank an @sanblch für das Hinzufügen der Unterstützung.)
Um Ihr Debuggen zu erleichtern, versuchen Sie, Ihre Lambda -Funktionskörper in separate Zeilen zu schreiben, damit Sie Bruchpunkte darin festlegen können.
pattern | xyz = [&]
{
// Separate lines for function body <- set break points here
}ist viel debuggierfreundlicher im Vergleich zu
pattern | xyz = [&] { /* some codes here */ }, // <- Set break points here, you will debug into the library.Debuggen Sie nicht in diese Bibliothek, es sei denn, Sie entscheiden sich wirklich, einige Fehler in dieser Bibliothek zu verursachen / zu beheben, genau wie Sie nicht in die STL -Variante oder in Sortimes debuggen werden.
Bitte versuchen Sie, ein minimales Beispiel zu erstellen, um die Probleme zu reproduzieren, die Sie getroffen haben. Sie können das Problem auf diese Weise schneller verursachen.
Sie können auch ein Problem in diesem Repo erstellen und die minimalen Beispielcodes anschließen. Ich werde versuchen, so schnell wie möglich zu reagieren (manchmal erwarten Sie eine Verzögerung von einer oder zwei Tagen).
Für Syntax -Designdetails finden Sie unter Referenz.
Das Dokument von Rost zu passend (IT) enthält gleichwertige Stichproben für entsprechende Rostproben.
Dort haben Sie vielleicht ein Bild davon, wie Codierung mit match(it) aussehen würde.
Das Dokument aus dem Muster -Matching -Vorschlag zu übereinstimmen (IT) enthält äquivalente Stichproben für entsprechende Stichproben im Match -Muster -Matching -Vorschlag.
Dort sehen Sie die Vor- und Nachteile der Bibliothek über den Vorschlag.
Beginnen wir eine Reise auf der Bibliothek!
(Für vollständige Stichproben finden Sie im Probenverzeichnis.)
Das folgende Beispiel zeigt, wie die Fakultät mit der match(it) implementiert wird.
# include " matchit.h "
constexpr int32_t factorial ( int32_t n)
{
using namespace matchit ;
assert (n >= 0 );
return match (n)(
pattern | 0 = 1 ,
pattern | _ = [n] { return n * factorial (n - 1 ); }
);
}Die grundlegende Syntax für die Musteranpassung ist
match (VALUE)
(
pattern | PATTERN1 = HANDLER1,
pattern | PATTERN2 = HANDLER2,
...
)Dies ist ein Funktionsaufruf und gibt einige von Handlern zurückgegebene Wert zurück. Der Rückgabetyp ist der gängige Typ für alle Handler. Der Rückgabetyp ist ungültig, wenn alle Handler die Werte zurückgeben. Inkompatible Rückgabetypen von mehreren Handlern sind ein Kompilierfehler. Wenn Handler Werte zurückgeben, müssen die Muster erschöpfend sein. Ein Laufzeitfehler tritt auf, wenn nicht alle Muster übereinstimmen. Es ist kein Fehler, wenn die Rückgabetypen von Handlern alle ungültig sind.
Der Handler kann auch ein Wert oder eine ID -Variable sein. 1 entspricht []{return 1;} .
Die Wildcard _ entspricht den Werten. Es ist eine übliche Praxis, es immer als das letzte Muster zu verwenden, das in unserer Bibliothek die gleiche Rolle wie für default case für switch -Anweisungen spielt, um zu vermeiden, dass ein Fall entkommt.
Wir können gleichzeitig mehrere Werte entsprechen:
# include " matchit.h "
constexpr int32_t gcd ( int32_t a, int32_t b)
{
using namespace matchit ;
return match (a, b)(
pattern | ds (_, 0 ) = [&] { return a >= 0 ? a : -a; },
pattern | _ = [&] { return gcd (b, a%b); }
);
}
static_assert (gcd( 12 , 6 ) == 6); Beachten Sie, dass einige Muster die Costexpr -Übereinstimmung unterstützen, dh Sie können sie zur Kompilierungszeit übereinstimmen. Aus den oben genannten Codeausschnitten können wir sehen, dass gcd(12, 6) in Kompilierungszeit ausgeführt werden kann.
Unterscheidet sich von Matching -Mustern in anderen Programmiersprachen, Variablen können normalerweise in den Muster in match(it) verwendet werden . Dies wird in der folgenden Stichprobe angezeigt:
# include " matchit.h "
# include < map >
template < typename Map, typename Key>
constexpr bool contains (Map const & map, Key const & key)
{
using namespace matchit ;
return match (map. find (key))(
pattern | map. end () = false ,
pattern | _ = true
);
}Wir können das Prädikatmuster verwenden, um einige Einschränkungen des zu übereinstimmenden Werts einzulegen.
constexpr double relu ( double value)
{
return match (value)(
pattern | (_ >= 0 ) = value,
pattern | _ = 0
);
}
static_assert (relu( 5 ) == 5);
static_assert (relu(- 5 ) == 0); Wir überladen einige Operatoren für Wildcard -Symbolen _ um die Verwendung grundlegender Prädikate zu erleichtern.
Manchmal möchten wir einen Handler für mehrere Muster teilen, oder Muster ist die Rettung:
# include " matchit.h "
constexpr bool isValid ( int32_t n)
{
using namespace matchit ;
return match (n)(
pattern | or_ ( 1 , 3 , 5 ) = true ,
pattern | _ = false
);
}
static_assert (isValid( 5 ));
static_assert (!isValid( 6 ));Und Muster dient zum Kombinieren mehrerer Prädikatmuster.
Das App -Muster ist leistungsstark, wenn Sie einige Informationen aus dem Motiv extrahieren möchten. Seine Syntax ist
app (PROJECTION, PATTERN)Eine einfache Probe, um zu überprüfen, ob eine Numfang groß ist, kann sein:
# include " matchit.h "
constexpr bool isLarge ( double value)
{
using namespace matchit ;
return match (value)(
pattern | app (_ * _, _ > 1000 ) = true ,
pattern | _ = false
);
}
// app with projection returning scalar types is supported by constexpr match.
static_assert (isLarge( 100 )); Beachten Sie, dass _ * _ ein Funktionsobjekt generiert, das das Quadrat der Eingabe berechnet, als Kurzversion von [](auto&& x){ return x*x;} angesehen werden kann.
Können wir den Wert binden, wenn wir sie bereits extrahiert haben? Sicher, das Identifikator -Muster ist genau das Richtige für Sie.
Lassen Sie uns das quadratische Ergebnis mit dem Kennungsmuster der Codes protokollieren
# include < iostream >
# include " matchit.h "
bool checkAndlogLarge ( double value)
{
using namespace matchit ;
Id< double > s;
return match (value)(
pattern | app (_ * _, s. at (_ > 1000 )) = [&] {
std::cout << value << " ^2 = " << *s << " > 1000! " << std::endl;
return true ; },
pattern | _ = false
);
} Um die Identifikatormuster zu verwenden, müssen wir zuerst die Kennungen ( Id<double> s ) definieren/deklarieren . (Markieren Sie es nicht als Konst.) Dies kann etwas seltsam sein, wenn Sie Identifikator -Muster in anderen Programmiersprache verwenden. Dies liegt an der Sprachbeschränkung. Aber sei nicht verärgert. Diese zusätzliche Ausführlichkeit ermöglicht es uns , Variablen in Mustern zu verwenden . Möglicherweise können Sie dies in anderen Programmiersprache möglicherweise nie tun.
Hier * wird der Bediener verwendet, um den Wert innerhalb von Kennungen zu Dereference. Eine Sache zu beachten ist, dass die Kennungen nur im match gültig sind. Versuchen Sie nicht, es draußen zu Dereference.
Id::at ähnelt dem @ -Muster in Rost, dh den Wert binden, wenn das Subpattern übereinstimmt.
Beachten Sie auch, dass die gebundenen Werte über operator== gleichzeitig gleichzeitig die gleiche Kennung gebunden sind. Eine Probe zum Überprüfen, ob ein Array symmetrisch ist:
# include " matchit.h "
constexpr bool symmetric (std::array< int32_t , 5 > const & arr)
{
using namespace matchit ;
Id< int32_t > i, j;
return match (arr)(
pattern | ds (i, j, _, j, i) = true ,
pattern | _ = false
);
}
static_assert (symmetric(std::array< int32_t , 5 >{ 5 , 0 , 3 , 7 , 10 }) == false);
static_assert (symmetric(std::array< int32_t , 5 >{ 5 , 0 , 3 , 0 , 5 }) == true);
static_assert (symmetric(std::array< int32_t , 5 >{ 5 , 1 , 3 , 0 , 5 }) == false); Jetzt kommen wir zu den mächtigsten Teilen: Zerstörungsmuster . Das Zerstörungsmuster kann für std::tuple , std::pair , std::array (Behälter mit fester std::begin ) und dynamische Container oder Größe ( std::vector , std::list , std::set std::end ) verwendet werden.
Das äußerste ds -Innenmuster kann weggelassen werden. Wenn das Muster mehrere Parameter empfängt, werden sie als Untermaterial eines DS -Musters behandelt.
# include " matchit.h "
template < typename T1, typename T2>
constexpr auto eval (std::tuple< char , T1, T2> const & expr)
{
using namespace matchit ;
Id<T1> i;
Id<T2> j;
return match (expr)(
pattern | ds ( ' + ' , i, j) = i + j,
pattern | ds ( ' - ' , i, j) = i - j,
pattern | ds ( ' * ' , i, j) = i * j,
pattern | ds ( ' / ' , i, j) = i / j,
pattern | _ = []
{
assert ( false );
return - 1 ;
});
} Einige Operatoren wurden für Id überlastet, daher gibt i + j eine nulläre Funktion zurück, die den Wert von *i + *j zurückgibt.
Es gibt auch Möglichkeiten, Ihre Struktur / Klasse zu zerstören, Ihre Struktur / Klasse-Tupel-ähnlich zu machen oder App-Muster zu übernehmen. Die zweite Option sieht aus wie
// Another option to destructure your struct / class.
constexpr auto dsByMember (DummyStruct const &v)
{
using namespace matchit ;
// compose patterns for destructuring struct DummyStruct.
constexpr auto dsA = dsVia (&DummyStruct::size, &DummyStruct::name);
Id< char const *> name;
return match (v)(
pattern | dsA ( 2 , name) = name,
pattern | _ = " not matched "
);
};
static_assert (dsByMember(DummyStruct{ 1 , " 123 " }) == std::string_view{ " not matched " });
static_assert (dsByMember(DummyStruct{ 2 , " 123 " }) == std::string_view{ " 123 " });Lassen Sie uns die Reise fortsetzen. Manchmal haben Sie mehrere Kennungen und möchten eine Einschränkung auf die Beziehung von ihnen ausüben. Ist das möglich? Sicher! Hier kommt der Match Guard . Seine Syntax ist
pattern | PATTERN | when(GUARD) = HANDLERSagen Sie, wir möchten nur dann übereinstimmen, wenn die Summe von zwei Kennungen einem Wert entspricht, können wir Codes als schreiben
# include < array >
# include " matchit.h "
constexpr bool sumIs (std::array< int32_t , 2 > const & arr, int32_t s)
{
using namespace matchit ;
Id< int32_t > i, j;
return match (arr)(
pattern | ds (i, j) | when (i + j == s) = true ,
pattern | _ = false
);
}
static_assert (sumIs(std::array< int32_t , 2 >{ 5 , 6 }, 11 )); Das ist cool, nicht wahr? Beachten Sie, dass i + j == s eine nulläre Funktion zurückgibt, die das Ergebnis von *i + *j == s zurückgibt.
Jetzt kommen wir zum OOO -Muster . Was ist das? Sie können fragen. In einer Programmiersprache wird es als Ruhemuster bezeichnet. Sie können eine willkürliche Anzahl von Elementen damit übereinstimmen. Es kann jedoch nur in ds -Mustern verwendet werden, und höchstens ein OOO -Muster kann in einem ds -Muster erscheinen. Sie können den Code wie folgt schreiben, wenn Sie das Muster eines Tupels überprüfen möchten.
# include < array >
# include " matchit.h "
template < typename Tuple>
constexpr int32_t detectTuplePattern (Tuple const & tuple)
{
using namespace matchit ;
return match (tuple)
(
pattern | ds ( 2 , ooo, 2 ) = 4 ,
pattern | ds ( 2 , ooo ) = 3 ,
pattern | ds (ooo, 2 ) = 2 ,
pattern | ds (ooo ) = 1
);
}
static_assert (detectTuplePattern(std::make_tuple( 2 , 3 , 5 , 7 , 2 )) == 4); Darüber hinaus können wir einen Unterabschnitt an das OOO -Muster binden, wenn wir ein std::array oder andere Behälter / Bereiche zerstören. Das ist ziemlich cool. Wir können überprüfen, ob an/ein array/vector/list/set/map/subrange/... symmetrisch ist, mit:
template < typename Range>
constexpr bool recursiveSymmetric (Range const &range)
{
Id< int32_t > i;
Id<SubrangeT<Range const >> subrange;
return match (range)(
pattern | ds (i, subrange. at (ooo), i) = [&] { return recursiveSymmetric (*subrange); },
pattern | ds (_, ooo, _) = false ,
pattern | _ = true
);Im ersten Muster benötigen wir, dass der Kopf dem Ende entspricht. Und wenn dies der Fall ist, überprüfen wir die REST -Teile (an unterteilig gebunden) über einen rekursiven Anruf. Sobald ein verschachtelter Anruf diese Anforderung nicht erfüllt (bis zum zweiten Muster), schlägt die Überprüfung fehl. Andernfalls, wenn nur noch ein Element übrig ist oder die Reichweite Null ist, wird das letzte Muster übereinstimmen, wir geben True zurück.
Wir haben mit unseren Kernmustern fertig. Beginnen wir nun die Reise des Verfassens von Mustern .
Sie sollten mit einem Muster und keinem Muster vertraut sein, wenn Sie die Musteranpassungsfunktion in Rost verwendet haben.
Einige / Keine Muster können verwendet werden, um Rohzeiger, std::optional , std::unique_ptr , std::shared_ptr und andere Typen, die in BOOL und Derferene konvertiert werden können. Eine typische Probe kann sein
# include " matchit.h "
template < typename T>
constexpr auto square (std::optional<T> const & t)
{
using namespace matchit ;
Id<T> id;
return match (t)(
pattern | some (id) = id * id,
pattern | none = 0
);
}
constexpr auto x = std::make_optional( 5 );
static_assert (square(x) == 25);Einige Muster akzeptieren ein Untermaterial. In der Probe ist das Subpatter eine Kennung und wir binden das Derferene Ergebnis. Kein Muster ist allein.
Einige und keine Muster sind keine atomaren Muster in match(it) , sie werden durch komponiert
template < typename T>
constexpr auto cast = []( auto && input) {
return static_cast <T>(input);
};
constexpr auto deref = []( auto &&x) { return *x; };
constexpr auto some = []( auto const pat) {
return and_ ( app (cast< bool >, true ), app (deref, pat));
};
constexpr auto none = app(cast< bool >, false );Für ein Muster haben wir zuerst den Wert auf einen booleschen Wert gewährt, wenn der boolesche Wert wahr ist, können wir ihn weiter Dereference können. Andernfalls schlägt das Match fehl. Für keiner Muster prüfen wir einfach, ob der konvertierte Boolesche Wert falsch ist.
Als Muster ist sehr nützlich für den Umgang sum type , einschließlich Klassenhierarchien, std::variant und std::any . std::variant und std::any besucht werden als
# include " matchit.h "
template < typename T>
constexpr auto getClassName (T const & v)
{
using namespace matchit ;
return match (v)(
pattern | as< char const *>(_) = " chars " ,
pattern | as< int32_t >(_) = " int32_t "
);
}
constexpr std::variant< int32_t , char const *> v = 123 ;
static_assert (getClassName(v) == std::string_view{ " int32_t " });Klassenhierarchien können als angepasst werden
struct Shape
{
virtual ~Shape () = default ;
};
struct Circle : Shape {};
struct Square : Shape {};
auto getClassName (Shape const &s)
{
return match (s)(
pattern | as<Circle>(_) = " Circle " ,
pattern | as<Square>(_) = " Square "
);
}Da Muster auch kein Atommuster ist. Es wird über über komponiert
template < typename T>
constexpr AsPointer<T> asPointer;
template < typename T>
constexpr auto as = []( auto const pat) {
return app (asPointer<T>, some (pat));
}; Für Klassen wird dynamic_cast standardmäßig als Muster verwendet, aber wir können das Verhalten durch den Anpassungspunkt ändern. Benutzer können das Down -Casting anpassen, indem sie eine get_if -Funktion für ihre Klassen definieren, ähnlich wie bei std::get_if für std::variant :
# include < iostream >
# include " matchit.h "
enum class Kind { kONE , kTWO };
class Num
{
public:
virtual ~Num () = default ;
virtual Kind kind () const = 0;
};
class One : public Num
{
public:
constexpr static auto k = Kind:: kONE ;
Kind kind () const override { return k; }
};
class Two : public Num
{
public:
constexpr static auto k = Kind:: kTWO ;
Kind kind () const override
{
return k;
}
};
template <Kind k>
constexpr auto kind = app(&Num::kind, k);
template < typename T>
auto get_if (Num const * num) {
return static_cast <T const *>(num-> kind () == T::k ? num : nullptr );
}
int32_t staticCastAs (Num const & input)
{
using namespace matchit ;
return match (input)(
pattern | as<One>(_) = 1 ,
pattern | kind<Kind:: kTWO > = 2 ,
pattern | _ = 3
);
}
int32_t main ()
{
std::cout << staticCastAs (One{}) << std::endl;
return 0 ;
}Es gibt einen zusätzlichen Zollpunkt .
Benutzer können PatternTraits spezialisieren, wenn sie ein brandneues Muster hinzufügen möchten.
Eine Sache zu beachten ist, dass Id kein einfacher Typ ist. Alle Kopien davon sind nur Verweise darauf. Versuchen Sie also nicht, es aus dem Ort zurückzugeben, an dem es definiert ist.
Ein schlechter Fall wäre
auto badId ()
{
Id< int > x;
return x;
} Die Rückgabe eines komponierten Musters einschließlich einer lokalen Id ist ebenfalls falsch.
auto badPattern ()
{
Id< int > x;
return composeSomePattern (x);
} Gute Praxis ist es, die Id nahe an der Verwendung in der Musteranpassung zu definieren.
auto goodPattern ()
{
Id< int > x;
auto somePattern = composeSomePattern (x);
return match (...)
(
pattern | somePattern = ...
);
} mathiu ist ein einfaches Computer -Algebra -System, das auf match(it) basiert.
Ein einfaches Beispiel von mathiu :
auto const x = symbol( " x " );
auto const e = x ^ fraction( 2 , 3 );
auto const d = diff(e, x);
// prints (* 2/3 (^ x -1/3))
std::cout << toString(d) << std::endl;OpenNask: Ein 80x86 Assembler wie Masm/Nasm für das winzige Betriebssystem.
Wenn Sie andere Projekte mit dieser Bibliothek kennen, teilen Sie mir bitte ein Problem oder eine PR mit.
Wenn Sie Fragen oder Ideen zur Bibliothek haben, öffnen Sie bitte ein Problem.
Diskussionen / Themen / PRs sind alle willkommen.
Die Syntax- / Musterdesigns von match(it) wurden stark von diesen damit verbundenen Arbeiten beeinflusst
Wenn Sie an match(it) interessiert sind, interessieren Sie sich möglicherweise auch für HSPP, das die Programmierung im Haskell -Stil auf C ++ bringt.
Bitte spielen Sie das Repo mit, teilen Sie das Repo oder sponsern Sie einen Dollar, um mir diese Bibliothek zu informieren.
Vielen Dank an alle, die Code beigetragen und Fehler gesendet haben.
Insbesondere dank der folgenden Mitwirkenden:
Hugo Etchegoyen (@hugoetchegoyen)
Vielen Dank an @e-dant für das Sponsoring dieses Projekts.