Facile à commencer.
Bibliothèque d'en-tête unique.
API macro-sans.
Aucune allocation de mémoire de tas.
Portabilité: testé en continu sous Ubuntu, macOS et Windows à l'aide de GCC / Clang / MSVC.
Pas de dépendances externes.
Fiabilité: Option de vérification du compilateur strict + désinfectants + Valgrind.
Modèles composables.
Extensible, les utilisateurs peuvent définir leurs propres modèles, soit en composant ceux existants, soit en créer de nouveaux.
Soutenir les conteneurs de type tuple et de type gamme.
Support partiel pour l'expression constante.
matchit.h Téléchargez simplement le fichier d'en-tête matchit.h et placez-le dans votre répertoire inclue pour les dépendances.
C'est ça.
Vous pouvez télécharger via cette commande bash
wget https://raw.githubusercontent.com/BowenFu/matchit.cpp/main/include/matchit.hIncluez l'extrait de code dans votre cMakelists.txt:
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} " ) Et ajouter ${matchit_SOURCE_DIR}/include à votre chemin d'inclusion.
Remplacez main par la dernière balise de version pour éviter la rupture de la compatibilité des API.
Cloner le repo via
git clone --depth 1 https://github.com/BowenFu/matchit.cpp
Installez la bibliothèque via
cd matchit.cpp
cmake -B ./build
cd build
make install
Ensuite, utilisez Find_package dans votre CMakelists.txt.
(Merci à @ daljit97 pour avoir ajouté le support.)
vcpkg install matchit
Maintenant, la bibliothèque a été soumise à Conan Center Index.
Vous pouvez désormais installer la bibliothèque via Conan.
(Merci à @sanblch pour avoir ajouté le support.)
Pour faciliter votre débogage, essayez d'écrire votre corps de fonction lambda en lignes distinctes afin que vous puissiez y installer des points de rupture.
pattern | xyz = [&]
{
// Separate lines for function body <- set break points here
}est beaucoup plus convivial par rapport à
pattern | xyz = [&] { /* some codes here */ }, // <- Set break points here, you will debug into the library.Ne déboguez pas dans cette bibliothèque à moins que vous ne décidiez vraiment d'instaurer / corriger certains bogues dans cette bibliothèque, tout comme vous ne déboguerez pas dans la variante STL ou les gammes.
Veuillez essayer de créer un échantillon minimal pour reproduire les problèmes que vous avez rencontrés. Vous pouvez enraciner provoquer le problème plus rapidement de cette manière.
Vous pouvez également créer un problème dans ce dépôt et joindre les exemples de codes minimaux et j'essaierai de répondre dès que possible (parfois, attendez-vous à un ou deux jours de retard).
Pour les détails de la conception de la syntaxe, veuillez vous référer à la référence.
Le document de Rust to Match (IT) donne des échantillons équivalents pour les échantillons de rouille correspondants.
Là, vous pouvez avoir une image du codage avec match(it) .
Le document de la proposition d'appariement de motif pour correspondre (IT) donne des échantillons équivalents pour les échantillons correspondants dans la proposition de correspondance de modèle de correspondance.
Là, vous verrez les avantages et les inconvénients de la bibliothèque sur la proposition.
Commençons un voyage dans la bibliothèque!
(Pour les échantillons complets, veuillez vous référer au répertoire des échantillons.)
L'exemple suivant montre comment implémenter la bibliothèque factorielle à l'aide match(it) .
# 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 ); }
);
}La syntaxe de base pour l'appariement des motifs est
match (VALUE)
(
pattern | PATTERN1 = HANDLER1,
pattern | PATTERN2 = HANDLER2,
...
)Il s'agit d'un appel de fonction et renverra une valeur renvoyée par les gestionnaires. Le type de retour est le type commun pour tous les gestionnaires. Le type de retour sera vide si tous les gestionnaires ne renvoient pas de valeurs. Les types de retour incompatibles de plusieurs gestionnaires sont une erreur de compilation. Lorsque les gestionnaires renvoient des valeurs, les modèles doivent être exhaustifs. Une erreur d'exécution se produira si tous les modèles ne sont pas correspondants. Ce n'est pas une erreur si les types de retour des gestionnaires sont tous nuls.
Le gestionnaire peut également être une valeur ou une variable d'ID. 1 équivaut à []{return 1;} .
Le joker _ correspondra à toutes les valeurs. C'est une pratique courante de toujours l'utiliser comme dernier modèle, jouant le même rôle dans notre bibliothèque que default case pour les instructions switch , pour éviter l'échappement de cas.
Nous pouvons faire correspondre plusieurs valeurs en même temps:
# 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); Notez que certains modèles prennent en charge la correspondance constexpr, c'est-à-dire que vous pouvez les faire correspondre au moment de la compilation. À partir des extraits de code ci-dessus, nous pouvons voir que gcd(12, 6) peut être exécuté en temps de compilation.
Différentes des modèles d'appariement dans d'autres langages de programmation, les variables peuvent être utilisées normalement à l'intérieur des modèles de match(it) , il est montré dans l'échantillon suivant:
# 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
);
}Nous pouvons utiliser le modèle de prédicat pour mettre certaines restrictions sur la valeur à correspondre.
constexpr double relu ( double value)
{
return match (value)(
pattern | (_ >= 0 ) = value,
pattern | _ = 0
);
}
static_assert (relu( 5 ) == 5);
static_assert (relu(- 5 ) == 0); Nous surchargeons certains opérateurs pour le symbole de la carte générique _ pour faciliter l'utilisation des prédicats de base.
Parfois, nous voulons partager un gestionnaire pour plusieurs modèles, ou le modèle est le sauvetage:
# 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 ));Et le modèle est pour combiner plusieurs modèles de prédicat.
Le modèle d'application est puissant lorsque vous souhaitez extraire certaines informations du sujet. Sa syntaxe est
app (PROJECTION, PATTERN)Un échantillon simple pour vérifier si un NUM est grand peut être:
# 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 )); Notez que _ * _ génère un objet de fonction qui calcule le carré de l'entrée, peut être considéré comme la version courte de [](auto&& x){ return x*x;} .
Pouvons-nous lier la valeur si nous les avons déjà extraits? Bien sûr, le modèle d'identifiant est pour vous.
Enregistrons le résultat carré, avec le modèle d'identifiant les codes
# 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
);
} Pour utiliser les modèles d'identifiant, nous devons d'abord définir / déclarer les identificateurs ( Id<double> s ) . (Ne le marquez pas comme const.) Cela peut être un peu étrange si vous utilisez des modèles d'identificateur dans un autre langage de programmation. Cela est dû à la restriction linguistique. Mais ne soyez pas bouleversé. Cette verbosité ajoutée nous permet d' utiliser des variables à l'intérieur des modèles . Vous ne pourrez peut-être jamais le faire dans un autre langage de programmation.
Ici * l'opérateur est utilisé pour déréférence la valeur à l'intérieur des identifiants. Une chose à noter est que les identifiants ne sont valables que la portée match . N'essayez pas de le déréférence à l'extérieur.
Id::at est similaire au motif @ dans la rouille, c'est-à-dire lier la valeur lorsque le sous-plier est assorti.
Notez également que lorsque le même identifiant est lié plusieurs fois, les valeurs liées doivent être égales les unes aux autres via operator== . Un échantillon pour vérifier si un tableau est symétrique:
# 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); Maintenant, nous arrivons aux parties les plus puissantes: le modèle de destruction . Le motif de destructure peut être utilisé pour std::tuple , std::pair , std::array (conteneurs de taille fixe) et des conteneurs dynamiques ou des gammes de taille ( std::vector , std::list , std::set , etc.) avec std::begin et std::end Prise en charge.
Le motif intérieur ds le plus externe peut être omis. Lorsque le motif reçoit plusieurs paramètres, ils sont traités comme des sous-bassins d'un motif DS.
# 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 ;
});
} Certains opérateurs ont été surchargés pour Id , donc i + j renverra une fonction nulaire qui renvoie la valeur de *i + *j .
Il y a également des moyens de détruire votre structure / classe, de faire de votre structure / classe de type tuple ou d'adopter un modèle d'application. La deuxième option ressemble à
// 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 " });Continuons le voyage. Parfois, vous avez plusieurs identifiants et vous voulez exercer une restriction sur leur relation. Est-ce possible? Bien sûr! Voici le Match Guard . Sa syntaxe est
pattern | PATTERN | when(GUARD) = HANDLERDisons que nous voulons ne correspondre que lorsque la somme de deux identifiants égale à une certaine valeur, nous pouvons écrire des codes comme
# 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 )); C'est cool, non? Notez que i + j == s renverra une fonction nulaire qui renvoie le résultat de *i + *j == s .
Maintenant, nous arrivons au modèle OOO . Qu'est-ce que c'est? Vous pouvez demander. Dans un langage de programmation, cela s'appelle REST MOTEM . Vous pouvez faire correspondre le nombre arbitraire d'éléments avec. Il ne peut être utilisé qu'à l'intérieur des modèles ds et au plus un motif OOO peut apparaître à l'intérieur d'un motif ds . Vous pouvez écrire le code comme suivant lorsque vous souhaitez vérifier le modèle d'un tuple.
# 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); De plus, nous pouvons lier une sous-réparation au motif OOO lors de la détraction d'un std::array ou d'autres conteneurs / plages. C'est assez cool. Nous pouvons vérifier si AN / A array/vector/list/set/map/subrange/... est symétrique avec:
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
);Dans le premier modèle, nous avons besoin que la tête équivaut à la fin. Et si tel est le cas, nous vérifions davantage les pièces de repos (liées à la sous-réparation) via un appel récursif. Une fois qu'un appel imbriqué ne répond pas à cette exigence (passez au deuxième modèle), la vérification échoue. Sinon, lorsqu'il ne reste qu'un seul élément ou que la taille de la plage est nul, le dernier motif correspond, nous retournons vrai.
Nous avons fait avec nos modèles de base. Commençons maintenant le voyage de la composition des modèles .
Vous devriez être familier avec un motif et aucun motif si vous avez utilisé la fonction de correspondance de motif dans la rouille.
Certains modèles / aucun peuvent être utilisés pour correspondre aux pointeurs bruts, std::optional , std::unique_ptr , std::shared_ptr et autres types qui peuvent être convertis en bool et déréférencés. Un échantillon typique peut être
# 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);Un motif accepte un sous-plier. Dans l'échantillon, le sous-piste est un identifiant et nous lui lions le résultat déréférencé. Aucun modèle n'est seul.
Certains et aucun des modèles ne sont pas des modèles atomiques en match(it) , ils sont composés via
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 );Pour un modèle, nous avons d'abord jeté la valeur à une valeur booléenne, si la valeur booléenne est vraie, nous pouvons encore la dégérer. Sinon, le match échoue. Pour aucun modèle, nous vérifions simplement si la valeur booléenne convertie est fausse.
Car le modèle est très utile pour gérer sum type , y compris les hiérarchies de classe, std::variant et std::any . std::variant et std::any peut être visité comme
# 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 " });Les hiérarchies de classe peuvent être égalées comme
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 "
);
}Comme le motif n'est pas non plus un motif atomique. Il est composé via
template < typename T>
constexpr AsPointer<T> asPointer;
template < typename T>
constexpr auto as = []( auto const pat) {
return app (asPointer<T>, some (pat));
}; Pour les classes, dynamic_cast est utilisé par défaut pour le modèle AS, mais nous pouvons modifier le comportement via le point de personnalisation . Les utilisateurs peuvent personnaliser le casting en duvet via la définition d'une fonction get_if pour leurs classes, similaire à std::get_if pour 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 ;
}Il y a un point de douane supplémentaire.
Les utilisateurs peuvent spécialiser PatternTraits s'ils souhaitent ajouter un tout nouveau modèle.
Une chose à noter est que Id n'est pas un type simple. Toute copie de celui-ci ne fait que des références. N'essayez donc pas de le retourner d'où il est défini.
Un mauvais cas serait
auto badId ()
{
Id< int > x;
return x;
} Le retour d'un modèle composé, y compris une Id locale, est également incorrect.
auto badPattern ()
{
Id< int > x;
return composeSomePattern (x);
} La bonne pratique consiste à définir l' Id près de son utilisation dans la correspondance des motifs.
auto goodPattern ()
{
Id< int > x;
auto somePattern = composeSomePattern (x);
return match (...)
(
pattern | somePattern = ...
);
} mathiu est un simple système d'algèbre informatique construit sur match(it) .
Un simple échantillon de 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: un assembleur 80x86 comme MASM / NASM pour le minuscule OS.
Si vous êtes au courant d'autres projets utilisant cette bibliothèque, veuillez me le faire savoir en soumettant un problème ou un RP.
Si vous avez des questions ou des idées concernant la bibliothèque, veuillez ouvrir un problème.
Les discussions / problèmes / PRS sont tous les bienvenus.
Les conceptions de syntaxe / motifs match(it) ont été fortement influencées par ces travaux connexes
Si vous êtes intéressé par match(it) , vous pouvez également vous intéresser à HSPP qui apporte la programmation de style Haskell en C ++.
Veuillez jouer le dépôt, partager le repo ou parrainer un dollar pour me faire savoir que cette bibliothèque est importante.
Merci à tous d'avoir contribué le code et d'envoi de bogues.
En particulier, grâce aux contributeurs suivants:
Hugo EtchegeGoyen (@Hugoetchegoyen)
Merci à @ e-dant d'avoir parrainé ce projet.