簡単に始めることができます。
シングルヘッダーライブラリ。
マクロフリーAPI。
ヒープメモリの割り当てはありません。
移植性:GCC/Clang/MSVCを使用して、Ubuntu、MacOS、およびWindowsで継続的にテストされました。
外部依存関係はありません。
信頼性:厳格なコンパイラチェックオプション +サニタイザー + valgrind。
構成可能なパターン。
拡張可能なユーザーは、既存のものを作成するか、真新しいパターンを作成することで、独自のパターンを定義できます。
破壊的なタプルのような範囲の容器をサポートします。
一定の発現に対する部分的なサポート。
matchit.hダウンロードしますHeader File matchit.hをダウンロードして、依存関係のためのディレクトリに挿入するだけです。
それでおしまい。
このbashコマンドからダウンロードできます
wget https://raw.githubusercontent.com/BowenFu/matchit.cpp/main/include/matchit.hcmakelists.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} " ) ${matchit_SOURCE_DIR}/includeを追加します。
APIの互換性の破壊を避けるために、 main最新リリースタグに置き換えます。
Repoを介してクローンします
git clone --depth 1 https://github.com/BowenFu/matchit.cpp
ライブラリを介してインストールします
cd matchit.cpp
cmake -B ./build
cd build
make install
次に、cmakelists.txtでfind_packageを使用します。
(サポートを追加してくれた @daljit97に感謝します。)
vcpkg install matchit
現在、ライブラリはコナンセンターインデックスに提出されています。
これで、コナン経由でライブラリをインストールできます。
(サポートを追加してくれた@sanblchに感謝します。)
デバッグを簡単にするには、ラムダ機能本体を別々の行に書いて、ブレークポイントを設定できるようにしてください。
pattern | xyz = [&]
{
// Separate lines for function body <- set break points here
}に比べてはるかにデバッグに優しいです
pattern | xyz = [&] { /* some codes here */ }, // <- Set break points here, you will debug into the library.STLバリアントや範囲にデバッグしないように、このライブラリのいくつかのバグを根絶 /修正することを実際に決定しない限り、このライブラリにデバッグしないでください。
あなたが満たした問題を再現するために、最小限のサンプルを作成してみてください。そのように、問題をより迅速に根絶することができます。
また、このリポジトリで問題を作成して最小限のサンプルコードを添付することもできます。できるだけ早く応答しようとします(1日または2日の遅延を期待することもあります)。
構文設計の詳細については、参照を参照してください。
Rust to Match(IT)からのドキュメントは、対応する錆サンプルと同等のサンプルを提供します。
そこにはmatch(it)のコーディングがどのようなものかの写真があるかもしれません。
一致するパターンマッチング提案(IT)からのドキュメントは、一致パターンマッチング提案の対応するサンプルの同等のサンプルを提供します。
そこには、提案に関する図書館の長所と短所が表示されます。
図書館で旅を始めましょう!
(完全なサンプルについては、サンプルディレクトリを参照してください。)
次のサンプルは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 ); }
);
}パターンマッチングの基本的な構文はです
match (VALUE)
(
pattern | PATTERN1 = HANDLER1,
pattern | PATTERN2 = HANDLER2,
...
)これは関数呼び出しであり、ハンドラーによって返される価値を返します。リターンタイプは、すべてのハンドラーの一般的なタイプです。すべてのハンドラーが値を返さない場合、返品タイプは無効になります。複数のハンドラーからの互換性のないリターンタイプは、コンパイルエラーです。ハンドラーが値を返す場合、パターンは網羅的でなければなりません。すべてのパターンが一致しない場合、ランタイムエラーが発生します。ハンドラーの返品タイプがすべて無効である場合、それはエラーではありません。
ハンドラーは、値またはID変数でもあります。 1は[]{return 1;}に相当します。
ワイルドカード_任意の値と一致します。常に最後のパターンとして使用するのは一般的な慣行であり、 switchステートメントでdefault caseが行うのと同じ役割をライブラリで演奏し、ケースが逃げないようにします。
複数の値を同時に一致させることができます。
# 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);一部のパターンはConstexprマッチをサポートしていることに注意してください。つまり、コンパイル時にそれらを一致させることができます。上記のコードスニペットから、コンパイル時間でgcd(12, 6)を実行できることがわかります。
他のプログラミング言語のマッチングパターンとは異なり、変数はmatch(it)のパターン内で通常使用できます。これは、次のサンプルに示されています。
# 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
);
}述語パターンを使用して、一致する値にいくつかの制限を置くことができます。
constexpr double relu ( double value)
{
return match (value)(
pattern | (_ >= 0 ) = value,
pattern | _ = 0
);
}
static_assert (relu( 5 ) == 5);
static_assert (relu(- 5 ) == 0);基本的な述語の使用を容易にするために、ワイルドカードシンボル_の一部のオペレーターを過負荷にします。
複数のパターンについて1つのハンドラーを共有したい場合があります。パターンは救助です。
# 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 ));パターンは、複数の述語パターンを組み合わせるためのものです。
アプリパターンは、主題からいくつかの情報を抽出したい場合に強力です。その構文はです
app (PROJECTION, PATTERN)numが大きいかどうかを確認するための簡単なサンプル:
# 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 )); _ * _入力の正方形[](auto&& x){ return x*x;}計算する関数オブジェクトを生成することに注意してください。
すでにそれらを抽出している場合、値をバインドできますか?確かに、識別子パターンはあなたのためです。
正方形の結果を記録しましょう、識別子パターンでコードは
# 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
);
}識別子パターンを使用するには、最初に識別子( Id<double> s )を定義/宣言する必要があります。 (constとしてマークしないでください。)他のプログラミング言語で識別子パターンを使用する場合、これは少し奇妙です。これは言語制限によるものです。しかし、動揺しないでください。この追加の冗長性により、パターン内の変数を使用することができます。他のプログラミング言語でこれを行うことができない場合があります。
ここで*演算子は、識別子内の値を再参照するために使用されます。注意すべきことの1つは、識別子がmatchスコープ内でのみ有効であることです。外でそれを控えようとしないでください。
Id::at 、錆の@パターンに似ています。つまり、サブパターンが一致するときの値をバインドします。
また、同じ識別子が複数回バインドされている場合、拘束値はoperator==を介して互いに等しくなければなりません。配列が対称であるかどうかを確認するサンプル:
# 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);今、私たちは最も強力な部分、つまり破壊パターンになります。破壊パターンはstd::tuple 、 std::pair 、 std::array (固定サイズのコンテナ)、および動的コンテナまたはサイズの範囲( std::vector 、 std::list 、 std::setなど)にstd::begin std::endサポートを使用して使用できます。
パターン内部の最も外側のds省略できます。パターンが複数のパラメーターを受信すると、それらは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 ;
});
}一部の演算子はIdに対して過負荷になっているため、 i + j *i + *jの値を返すnulrary関数を返します。
また、構造体 /クラスを破壊したり、構造体 /クラスのタプルのようにしたり、アプリのパターンを採用する方法もあります。 2番目のオプションは次のように見えます
// 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 " });旅を続けましょう。複数の識別子がある場合があり、それらの関係に制限を及ぼしたい場合があります。それは可能ですか?もちろん!ここにマッチガードが来ます。その構文はです
pattern | PATTERN | when(GUARD) = HANDLERたとえば、2つの識別子の合計が何らかの値に等しい場合にのみ一致したいと考えています。
# 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 ));それはかっこいいですね。 i + j == sは*i + *j == sの結果を返すヌラリー関数を返すことに注意してください。
今、私たちはOOOパターンに来ます。それは何ですか?あなたは尋ねるかもしれません。一部のプログラミング言語では、 RESTパターンと呼ばれます。任意の数のアイテムと一致させることができます。ただし、 dsパターン内でのみ使用でき、せいぜい1つのOOOパターンがdsパターン内に表示される可能性があります。タプルのパターンを確認する場合、次のようにコードを記述できます。
# 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);さらに、 std::arrayまたはその他のコンテナ /範囲を破壊するときに、 SubrangeをOOOパターンにバインドできます。それはかなりクールです。 An/a array/vector/list/set/map/subrange/...対称であるかどうかを確認できます。
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
);最初のパターンでは、頭が最後まで等しくなる必要があります。そして、その場合は、再帰コールを介して残りの部分(サブランジに結合する)をさらに確認します。いくつかのネストされたコールがその要件を満たすことに失敗すると(2番目のパターンに落ちる)、チェックは失敗します。それ以外の場合は、要素が1つしかない場合、または範囲のサイズがゼロの場合、最後のパターンが一致し、trueを返します。
コアパターンを使用しました。次に、パターンを作曲する旅を始めましょう。
Rustでパターンマッチング機能を使用している場合、何らかのパターンとパターンに精通している必要があります。
一部の /なしパターンは、生のポインター、 std::optional 、 std::unique_ptr 、 std::shared_ptr 、およびboolに変換して繰り返される他のタイプを一致させるために使用できます。典型的なサンプルは可能です
# 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);一部のパターンはサブパターンを受け入れます。サンプルでは、subpatternは識別子であり、控えめな結果を結合します。パターンは単独ではありません。
いくつかのパターンはmatch(it) 、それらは経由で構成されています
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 );いくつかのパターンの場合、最初に値をブール値にキャストします。ブール値が真である場合、さらに繰り返します。それ以外の場合、試合は失敗します。なしパターンの場合、変換されたブール値が偽であるかどうかを確認します。
パターンは、クラスの階層、 std::variant 、およびstd::any 、 sum typeを処理するのに非常に役立ちます。 std::variant and std::anyを訪れることができます
# 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 " });クラスの階層は、として一致させることができます
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 "
);
}パターンは原子パターンでもないためです。それは介して構成されています
template < typename T>
constexpr AsPointer<T> asPointer;
template < typename T>
constexpr auto as = []( auto const pat) {
return app (asPointer<T>, some (pat));
};クラスの場合、 dynamic_castデフォルトでパターンとして使用されますが、カスタマイズポイントを介して動作を変更できます。ユーザーはstd::get_if for std::variantに似たクラスのget_if関数を定義することで、ダウンキャスティングをカスタマイズできます。
# 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 ;
}追加のカスタムZiationポイントがあります。
ユーザーは、新しいパターンを追加したい場合は、 PatternTraitsを専門とすることができます。
注意すべきことの1つは、 Idがプレーンタイプではないということです。それのコピーはそれを参照するだけです。したがって、定義されている場所から返品しようとしないでください。
悪いケースはそうでしょう
auto badId ()
{
Id< int > x;
return x;
}ローカルIdを含む構成されたパターンを返すことも間違っています。
auto badPattern ()
{
Id< int > x;
return composeSomePattern (x);
}グッドプラクティスは、パターンマッチングでの使用に近いIdを定義することです。
auto goodPattern ()
{
Id< int > x;
auto somePattern = composeSomePattern (x);
return match (...)
(
pattern | somePattern = ...
);
}mathiu match(it) 。
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:小さなOSのMASM/NASMのような80x86アセンブラー。
このライブラリを使用している他のプロジェクトを知っている場合は、問題またはPRを提出してお知らせください。
ライブラリに関する質問やアイデアがある場合は、問題を開いてください。
ディスカッション /問題 / PRはすべて大歓迎です。
match(it)の構文 /パターンデザインは、これらの関連する作業の影響を強く受けています
match(it)に興味がある場合は、HaskellスタイルのプログラミングをC ++にもたらすHSPPにも興味があるかもしれません。
このライブラリが重要であることを知らせて、リポジトリに出演したり、リポジトリを共有したり、1ドルをスポンサーしてください。
コードを寄付し、バグを送信してくれたことに感謝します。
特に、次の貢献者に感謝します。
Hugo Etchegoyen(@hugoechegoyen)
このプロジェクトを後援してくれた @e-dantに感謝します。