Fácil de comenzar.
Biblioteca de encabezado único.
API de macro.
No hay asignación de memoria de montón.
Portabilidad: probado continuamente en Ubuntu, macOS y Windows usando GCC/Clang/MSVC.
No hay dependencias externas.
Confiabilidad: opción de verificación de compilador estricto + desinfectantes + Valgrind.
Patrones compuestos.
Extensible, los usuarios pueden definir sus propios patrones, ya sea componiendo los existentes o crear nuevos.
Apoya la destrucción de contenedores tipo tuple y de rango.
Apoyo parcial para la expresión constante.
matchit.h Simplemente descargue el archivo de encabezado matchit.h y póngalo en su directorio de incluido para dependencias.
Eso es todo.
Puede descargar a través de este comando bash
wget https://raw.githubusercontent.com/BowenFu/matchit.cpp/main/include/matchit.hIncluya el fragmento de código en su 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} " ) Y agregue ${matchit_SOURCE_DIR}/include a su ruta de incluido.
Reemplace main con la última etiqueta de lanzamiento para evitar la ruptura de la compatibilidad de la API.
Clonar el repositorio a través de
git clone --depth 1 https://github.com/BowenFu/matchit.cpp
Instale la biblioteca a través de
cd matchit.cpp
cmake -B ./build
cd build
make install
Luego use Find_package en su cmakelists.txt.
(Gracias a @daljit97 por agregar el soporte).
vcpkg install matchit
Ahora la biblioteca ha sido enviada al índice del Centro Conan.
Ahora puede instalar la biblioteca a través de Conan.
(Gracias a @sanblch por agregar el soporte).
Para facilitar su depuración, intente escribir su cuerpo de función Lambda en líneas separadas para que pueda establecer puntos de interrupción en él.
pattern | xyz = [&]
{
// Separate lines for function body <- set break points here
}es mucho más amigable para la depuración en comparación con
pattern | xyz = [&] { /* some codes here */ }, // <- Set break points here, you will debug into the library.No depugte en esta biblioteca a menos que realmente decida causar / solucionar algunos errores en esta biblioteca, al igual que no depugga en una variante o rangos STL.
Intente crear una muestra mínima para reproducir los problemas que ha conocido. Puede causar raíz del problema más rápidamente de esa manera.
También puede crear un problema en este repositorio y adjuntar los códigos de muestra mínimos e intentaré responder lo antes posible (a veces espere uno o dos días de retraso).
Para obtener detalles de diseño de sintaxis, consulte la referencia.
El documento de Rust a Match (IT) ofrece muestras equivalentes para las muestras de óxido correspondientes.
Allí puede tener una imagen de cómo sería la codificación con match(it) .
El documento de la propuesta de coincidencia de patrones para que coincida (IT) proporciona muestras equivalentes para las muestras correspondientes en la propuesta de coincidencia de patrones de coincidencia.
Allí verá los pros y los contras de la biblioteca sobre la propuesta.
¡Comencemos un viaje en la biblioteca!
(Para muestras completas, consulte el directorio de muestras).
La siguiente muestra muestra cómo implementar Factorial usando la biblioteca 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 sintaxis básica para la coincidencia de patrones es
match (VALUE)
(
pattern | PATTERN1 = HANDLER1,
pattern | PATTERN2 = HANDLER2,
...
)Esta es una llamada de función y devolverá algún valor devuelto por los controladores. El tipo de retorno es el tipo común para todos los manejadores. El tipo de retorno será nulo si todos los manejadores no devuelven valores. Los tipos de retorno incompatibles de múltiples manejadores son un error de compilación. Cuando los manejadores devuelven los valores, los patrones deben ser exhaustivos. Se producirá un error de tiempo de ejecución si todos los patrones no coinciden. No es un error si los tipos de retorno de los manejadores son todos nula.
El controlador también puede ser un valor o una variable de identificación. 1 es equivalente a []{return 1;} .
El comodín _ coincidirá con cualquier valor. Es una práctica común usarlo siempre como el último patrón, que juega el mismo papel en nuestra biblioteca que default case para las declaraciones de switch , para evitar el escape de casos.
Podemos igualar múltiples valores al mismo tiempo:
# 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); Tenga en cuenta que algunos patrones admiten un coincidencia de constexpr, es decir, puede igualarlos en el momento de la compilación. De los fragmentos de código anteriores, podemos ver que gcd(12, 6) se puede ejecutar en el tiempo de compilación.
A diferencia de los patrones coincidentes en otros lenguajes de programación, las variables se pueden usar normalmente dentro de los patrones en match(it) , esto se muestra en la siguiente muestra:
# 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
);
}Podemos usar el patrón de predicado para poner algunas restricciones en el valor que se coinciden.
constexpr double relu ( double value)
{
return match (value)(
pattern | (_ >= 0 ) = value,
pattern | _ = 0
);
}
static_assert (relu( 5 ) == 5);
static_assert (relu(- 5 ) == 0); Sobrecaremos a algunos operadores para un símbolo de comodín _ para facilitar el uso de predicados básicos.
A veces queremos compartir un controlador para múltiples patrones, o el patrón es el rescate:
# 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 ));Y el patrón es para combinar múltiples patrones de predicado.
El patrón de la aplicación es poderoso cuando desea extraer alguna información del sujeto. Su sintaxis es
app (PROJECTION, PATTERN)Una muestra simple para verificar si un número es grande puede ser:
# 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 )); Tenga en cuenta que _ * _ genera un objeto de función que calcula el cuadrado de la entrada, puede considerarse la versión corta de [](auto&& x){ return x*x;} .
¿Podemos unir el valor si ya los hemos extraído? Claro, el patrón de identificación es para ti.
Registremos el resultado cuadrado, con el patrón de identificadores, los códigos serían
# 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
);
} Para usar patrones de identificadores, primero debemos definir/declarar los identificadores ( Id<double> s ) . (No lo marque como const.) Esto puede ser un poco extraño si ha usado patrones de identificación en otro lenguaje de programación. Esto se debe a la restricción del idioma. Pero no te molestes. Esta verbosidad agregada nos permite usar variables dentro de los patrones . Es posible que nunca pueda hacer esto en otro lenguaje de programación.
Aquí * El operador se usa para desreferencia el valor interno de los identificadores. Una cosa a tener en cuenta es que los identificadores solo son válidos dentro del alcance match . No intentes desreferirlo afuera.
Id::at es similar al patrón @ en óxido, es decir, vincule el valor cuando el subpatrón se combina.
También tenga en cuenta cuándo el mismo identificador está atado varias veces, los valores unidos deben igual entre sí a través operator== . Una muestra para verificar si una matriz es simétrica:
# 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); Ahora llegamos a las partes más poderosas: el patrón de destrucción . El patrón de destrucción se puede usar para std::tuple , std::pair , std::array (contenedores de tamaño fijo) y contenedores dinámicos o rangos de tamaño ( std::vector , std::list , std::set , y así sucesivamente) con std::begin y std::end .
Se puede omitir el patrón interno ds más externo. Cuando el patrón recibe múltiples parámetros, se tratan como subpaterarios de un patrón 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 ;
});
} Algunos operadores han sido sobrecargados para Id , por lo que i + j devolverá una función nular que devuelve el valor de *i + *j .
También hay formas de destruir su estructura / clase, hacer que su estructura / clase de tuple o adoptar el patrón de aplicaciones. La segunda opción se ve como
// 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 " });Continuemos el viaje. A veces tiene múltiples identificadores y desea ejercer una restricción sobre la relación de ellos. ¿Es eso posible? ¡Seguro! Aquí viene el guardia del partido . Su sintaxis es
pattern | PATTERN | when(GUARD) = HANDLERDigamos que queremos coincidir solo cuando la suma de dos identificadores sea igual a algún valor, podemos escribir códigos como
# 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 )); Eso es genial, ¿no? Tenga en cuenta que i + j == s devolverá una función nular que devuelve el resultado de *i + *j == s .
Ahora llegamos al patrón OOO . ¿Qué es eso? Puedes preguntar. En algún lenguaje de programación se llama patrón de descanso . Puede igualar el número arbitrario de elementos con él. Sin embargo, solo se puede usar dentro de los patrones ds y, como máximo, un patrón OOO puede aparecer dentro de un patrón ds . Puede escribir el código como lo sigue cuando desee verificar el patrón de una tupla.
# 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); Además, podemos unir un subrangule al patrón OOO al destruir una std::array o otros contenedores / rangos. Eso es bastante genial. Podemos verificar si una array/vector/list/set/map/subrange/... es simétrica con:
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
);En el primer patrón, requerimos que la cabeza sea igual al final. Y si ese es el caso, verificamos aún más las partes del resto (unidas a subrangange) a través de una llamada recursiva. Una vez que alguna llamada anidada no cumple con ese requisito (cae al segundo patrón), la verificación falla. De lo contrario, cuando solo queda un elemento o el tamaño de rango es cero, el último patrón coincide, devolvemos verdadero.
Hemos terminado con nuestros patrones centrales. Ahora comencemos el viaje de componer patrones .
Debe estar familiarizado con algún patrón y ningún patrón si ha utilizado la función de coincidencia de patrones en Rust.
Los patrones de algunos / ninguno se pueden usar para que coincidan con punteros sin procesar, std::optional , std::unique_ptr , std::shared_ptr y otros tipos que se pueden convertir en bool y desferenciado. Una muestra típica puede ser
# 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);Algún patrón acepta un subpatrón. En la muestra, el subpatrón es un identificador y nos unimos el resultado desferenciado. Ninguno de los patrones está solo.
Algunos patrones y ninguno no son patrones atómicos en match(it) , se componen a través de
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 );Para algún patrón, primero arrojamos el valor a un valor booleano, si el valor booleano es verdadero, podemos desreferirlo. De lo contrario, el partido falla. Para ningún patrón, simplemente verificamos si el valor booleano convertido es falso.
Como el patrón es muy útil para manejar sum type , incluidas las jerarquías de clase, std::variant y std::any . std::variant y std::any se puede visitar como
# 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 " });Las jerarquías de clases se pueden combinar como
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 "
);
}Ya que el patrón tampoco es un patrón atómico. Está compuesto a través de
template < typename T>
constexpr AsPointer<T> asPointer;
template < typename T>
constexpr auto as = []( auto const pat) {
return app (asPointer<T>, some (pat));
}; Para las clases, dynamic_cast se usa de forma predeterminada para un patrón, pero podemos cambiar el comportamiento a través del punto de personalización . Los usuarios pueden personalizar el lanzamiento de abajo mediante la definición de una función get_if para sus clases, similar a std::get_if para 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 ;
}Hay un punto de personalización adicional.
Los usuarios pueden especializar PatternTraits si desean agregar un patrón nuevo.
Una cosa a tener en cuenta es que Id no es un tipo simple. Cualquier copia de él solo son referencias a él. Así que no intentes devolverlo desde donde se define.
Un mal caso sería
auto badId ()
{
Id< int > x;
return x;
} Devolver un patrón compuesto que incluye una Id local también es incorrecto.
auto badPattern ()
{
Id< int > x;
return composeSomePattern (x);
} La buena práctica es definir la Id cercana a su uso en la coincidencia de patrones.
auto goodPattern ()
{
Id< int > x;
auto somePattern = composeSomePattern (x);
return match (...)
(
pattern | somePattern = ...
);
} mathiu es un sistema de álgebra informático simple construido en match(it) .
Una simple muestra 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 ensamblador de 80x86 como MASM/NASM para el pequeño sistema operativo.
Si está al tanto de otros proyectos que usan esta biblioteca, hágamelo saber enviando un problema o un PR.
Si tiene alguna pregunta o idea sobre la biblioteca, abra un problema.
Las discusiones / temas / PRS son bienvenidos.
Los diseños de sintaxis / patrones match(it) han sido fuertemente influenciados por estos trabajos relacionados
Si está interesado en match(it) , también puede estar interesado en HSPP, lo que lleva la programación de estilo Haskell a C ++.
Por favor, estrella el repositorio, comparta el repositorio o patrocina un dólar para informarme que esta biblioteca es importante.
Gracias a todos por contribuir con código y enviar errores.
En particular, gracias a los siguientes contribuyentes:
Hugo Etchegoyen (@hugoetchegoyen)
Gracias a @e-dant por patrocinar este proyecto.