Fácil de começar.
Biblioteca de cabeçalho único.
APIs sem macro.
Sem alocação de memória de heap.
Portabilidade: testado continuamente no Ubuntu, MacOS e Windows usando GCC/CLANG/MSVC.
Sem dependências externas.
Confiabilidade: Opção de verificação rigorosa do compilador + desinfetantes + Valgrind.
Padrões composíveis.
Extensível, os usuários podem definir seus próprios padrões, através da composição de existentes ou criar novos.
Apoie os recipientes destrutivos de tupla e tipo de alcance.
Suporte parcial para expressão constante.
matchit.h Basta baixar o arquivo de cabeçalho matchit.h e colocá -lo no seu diretório incluir dependências.
É isso.
Você pode baixar através deste comando Bash
wget https://raw.githubusercontent.com/BowenFu/matchit.cpp/main/include/matchit.hInclua o snippet de código em seus 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} " ) E adicione ${matchit_SOURCE_DIR}/include no seu caminho de incluir.
Substitua main pela tag de liberação mais recente para evitar a quebra de compatibilidade da API.
Clonar o repo via
git clone --depth 1 https://github.com/BowenFu/matchit.cpp
Instale a biblioteca via
cd matchit.cpp
cmake -B ./build
cd build
make install
Em seguida, use find_package nos seus cmakelists.txt.
(Obrigado a @daljit97 por adicionar o suporte.)
vcpkg install matchit
Agora a biblioteca foi enviada ao Conan Center Index.
Agora você pode instalar a biblioteca via Conan.
(Obrigado a @sanblch por adicionar o suporte.)
Para facilitar sua depuração, tente escrever seu corpo de função lambda em linhas separadas, para que você possa definir pontos de interrupção.
pattern | xyz = [&]
{
// Separate lines for function body <- set break points here
}é muito mais amigável de depuração em comparação com
pattern | xyz = [&] { /* some codes here */ }, // <- Set break points here, you will debug into the library.Não depra nesta biblioteca, a menos que você realmente decida fazer a causa / corrigir alguns bugs nesta biblioteca, assim como você não depurará na variante STL ou na faixa.
Tente criar uma amostra mínima para reproduzir os problemas que você conheceu. Você pode fazer o root causando o problema mais rapidamente dessa maneira.
Você também pode criar um problema neste repositório e anexar os códigos de amostra mínimos e tentarei responder o mais rápido possível (às vezes, espere um ou dois dias de atraso).
Para detalhes do design da sintaxe, consulte a referência.
O documento da ferrugem para corresponder (It) fornece amostras equivalentes para amostras de ferrugem correspondentes.
Lá você pode ter uma imagem de como seria a codificação com match(it) .
O documento da proposta de correspondência de padrões para corresponder (It) fornece amostras equivalentes para amostras correspondentes na proposta de correspondência do padrão de correspondência.
Lá você verá os prós e contras da biblioteca sobre a proposta.
Vamos começar uma jornada na biblioteca!
(Para amostras completas, consulte o diretório de amostras.)
A amostra a seguir mostra como implementar o fatorial usando a 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 ); }
);
}A sintaxe básica para correspondência de padrões é
match (VALUE)
(
pattern | PATTERN1 = HANDLER1,
pattern | PATTERN2 = HANDLER2,
...
)Esta é uma chamada de função e retornará algum valor retornado pelos manipuladores. O tipo de retorno é do tipo comum para todos os manipuladores. O tipo de retorno será anulado se todos os manipuladores não retornarem valores. Tipos de retorno incompatíveis de vários manipuladores é um erro de compilação. Quando os manipuladores retornam valores, os padrões devem ser exaustivos. Um erro de tempo de execução acontecerá se todos os padrões não forem correspondentes. Não é um erro se os tipos de retorno dos manipuladores forem todos nulos.
O manipulador também pode ser um valor ou uma variável de identificação. 1 é equivalente a []{return 1;} .
O curinga _ corresponderá a quaisquer valores. É uma prática comum sempre usá -lo como o último padrão, desempenhando o mesmo papel em nossa biblioteca que default case para switch Declarações, para evitar escapar de casos.
Podemos corresponder a vários valores ao mesmo tempo:
# 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); Observe que alguns padrões suportam a correspondência do Constexpr, ou seja, você pode correspondê -los no momento da compilação. A partir dos trechos de código acima, podemos ver que gcd(12, 6) pode ser executado no tempo de compilação.
Diferente dos padrões de correspondência em outras linguagens de programação, as variáveis podem ser usadas normalmente padrões dentro da match(it) , isso é mostrado na amostra a seguir:
# 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 o padrão de predicado para colocar algumas restrições no valor a ser correspondido.
constexpr double relu ( double value)
{
return match (value)(
pattern | (_ >= 0 ) = value,
pattern | _ = 0
);
}
static_assert (relu( 5 ) == 5);
static_assert (relu(- 5 ) == 0); Sobrecarregamos alguns operadores do símbolo do curinga _ para facilitar o uso de predicados básicos.
Às vezes, queremos compartilhar um manipulador para vários padrões, ou o padrão é o resgate:
# 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 ));E o padrão é para combinar vários padrões de predicado.
O padrão de aplicativo é poderoso quando você deseja extrair algumas informações do assunto. Sua sintaxe é
app (PROJECTION, PATTERN)Uma amostra simples para verificar se um número é grande pode 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 )); Observe que _ * _ gera um objeto de função que calcula o quadrado da entrada, pode ser considerado a versão curta de [](auto&& x){ return x*x;} .
Podemos vincular o valor se já os extraímos? Claro, o padrão de identificador é para você.
Vamos registrar o resultado quadrado, com o padrão de identificador, os códigos seriam
# 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 padrões de identificador, precisamos definir/declarar os identificadores ( Id<double> s ) primeiro . (Não o marque como const.) Isso pode ser um pouco estranho se você usar padrões de identificador em outra linguagem de programação. Isso se deve à restrição do idioma. Mas não fique chateado. Essa verbosidade adicional possibilita que usemos variáveis dentro dos padrões . Você pode nunca ser capaz de fazer isso em outra linguagem de programação.
Aqui * O operador é usado para desreferenciar o valor dentro dos identificadores. Uma coisa a observar é que os identificadores são válidos apenas dentro do escopo match . Não tente desreferente -o lá fora.
Id::at é semelhante ao padrão @ em ferrugem, ou seja, vincule o valor quando o subpadrão for correspondido.
Observe também quando o mesmo identificador está ligado várias vezes, os valores de limite devem ser iguais a outro via operator== . Uma amostra para verificar se uma matriz é 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); Agora chegamos às partes mais poderosas: padrão de destruição . O padrão de destruição pode ser usado para std::tuple , std::pair , std::array (contêineres de tamanho fixo) e contêineres dinâmicos ou intervalos de tamanho ( std::vector , std::list , std::set , e assim por diante) com std::begin std::end suportes.
O padrão interno mais ds pode ser omitido. Quando o padrão recebe vários parâmetros, eles são tratados como subpadrões de um padrão 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 ;
});
} Alguns operadores foram sobrecarregados para Id , então i + j retornará uma função nular que retorna o valor de *i + *j .
Também existem maneiras de destruir sua estrutura / classe, faça seu padrão de aplicativo do tipo Struct / Class, tipo tupla ou adotar. A segunda opção se parece
// 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 " });Vamos continuar a jornada. Às vezes, você tem vários identificadores e deseja exercer uma restrição ao relacionamento deles. Isso é possível? Claro! Aí vem o guarda da partida . Sua sintaxe é
pattern | PATTERN | when(GUARD) = HANDLERDigamos, queremos corresponder apenas quando a soma de dois identificadores é igual a algum valor, podemos escrever 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 )); Isso é legal, não é? Observe que i + j == s retornará uma função nular que retornará o resultado de *i + *j == s .
Agora chegamos ao padrão OOO . O que é aquilo? Você pode perguntar. Em alguma linguagem de programação, é chamado de padrão de descanso . Você pode corresponder ao número arbitrário de itens com ele. No entanto, ele só pode ser usado dentro dos padrões ds e, no máximo, um padrão OOO pode aparecer dentro de um padrão ds . Você pode escrever o código como seguinte quando deseja verificar o padrão de uma 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); Além disso, podemos vincular uma subrange ao padrão OOO ao destruir um std::array ou outros contêineres / intervalos. Isso é muito legal. Podemos verificar se AN/A array/vector/list/set/map/subrange/... é simétrico com:
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
);No primeiro padrão, exigimos que a cabeça seja igual ao final. E se for esse o caso, verificamos ainda as peças de restos (vinculadas à subrange) por meio de uma chamada recursiva. Depois que alguma chamada aninhada não atende a esse requisito (cair no segundo padrão), a verificação falha. Caso contrário, quando restar apenas um elemento ou o tamanho do intervalo é zero, o último padrão é correspondido, retornamos verdadeiro.
Fizemos com nossos padrões principais. Agora vamos começar a jornada de compor padrões .
Você deve estar familiarizado com algum padrão e nenhum padrão se tiver usado o recurso de correspondência de padrões em ferrugem.
Alguns / nenhum padrões podem ser usados para combinar com ponteiros crus, std::optional , std::unique_ptr , std::shared_ptr e outros tipos que podem ser convertidos em bool e desreferenciados. Uma amostra típica pode 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);Algum padrão aceita um subpadrão. Na amostra, o subpadrão é um identificador e ligamos o resultado desreferenciado a ele. Nenhum padrão está sozinho.
Alguns e nenhum padrões não são padrões atômicos na match(it) , eles são compostos 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 );Para algum padrão, primeiro lançamos o valor para um valor booleano, se o valor booleano for verdadeiro, podemos desreferenciá -lo ainda mais. Caso contrário, a partida falha. Para nenhum padrão, simplesmente verificamos se o valor booleano convertido é falso.
Como o padrão é muito útil para lidar com sum type , incluindo hierarquias de classe, std::variant e std::any . std::variant e std::any pode ser visitado 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 " });Hierarquias de classe podem ser correspondidas 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 "
);
}Como o padrão também não é um padrão atômico. É composto via
template < typename T>
constexpr AsPointer<T> asPointer;
template < typename T>
constexpr auto as = []( auto const pat) {
return app (asPointer<T>, some (pat));
}; Para as classes, dynamic_cast é usado por padrão para o padrão, mas podemos alterar o comportamento através do ponto de personalização . Os usuários podem personalizar o lançamento do Down através da definição de uma função get_if para suas classes, semelhante ao 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 ;
}Há um ponto adicional de customziação .
Os usuários podem especializar PatternTraits se desejarem adicionar um novo padrão.
Uma coisa a observar é que Id não é um tipo simples. Quaisquer cópias disso são apenas referências a ele. Portanto, não tente devolvê -lo de onde está definido.
Um caso ruim seria
auto badId ()
{
Id< int > x;
return x;
} Retornar um padrão composto, incluindo um Id local, também está incorreto.
auto badPattern ()
{
Id< int > x;
return composeSomePattern (x);
} A boa prática é definir o Id próximo ao seu uso na correspondência de padrões.
auto goodPattern ()
{
Id< int > x;
auto somePattern = composeSomePattern (x);
return match (...)
(
pattern | somePattern = ...
);
} mathiu é um sistema de álgebra de computador simples construído na match(it) .
Uma amostra simples 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: um assembler de 80x86 como MASM/NASM para o pequeno sistema operacional.
Se você estiver ciente de outros projetos usando esta biblioteca, informe -me enviando um problema ou um PR.
Se você tiver alguma dúvida ou idéias sobre a biblioteca, abra um problema.
Discussões / questões / PRs são todos bem -vindos.
Os projetos de sintaxe / padrões match(it) foram fortemente influenciados por esses trabalhos relacionados
Se você estiver interessado em match(it) , também pode estar interessado no HSPP, o que traz programação no estilo Haskell para C ++.
Estrela o repositório, compartilhe o repositório ou patrocine um dólar para me informar que essa biblioteca é importante.
Obrigado a todos por contribuir com código e enviar bugs.
Em particular, graças aos seguintes colaboradores:
Hugo Etchegoyen (@HugotheGoyen)
Obrigado ao @e-Dant por patrocinar este projeto.