容易開始。
單頭庫。
無宏的API。
沒有堆內存分配。
可移植性:使用GCC/Clang/MSVC在Ubuntu,MacOS和Windows下進行連續測試。
沒有外部依賴性。
可靠性:嚴格的編譯器檢查選項 +消毒劑 + Valgrind。
合併模式。
可擴展的用戶可以通過組成現有的模式來定義自己的模式,或者創建全新的模式。
支持破壞借助元組和類似射程的容器。
部分支持恆定表達。
matchit.h只需下載標題文件matchit.h ,然後將其放入依賴項目錄中。
就是這樣。
您可以通過此bash命令下載
wget https://raw.githubusercontent.com/BowenFu/matchit.cpp/main/include/matchit.h在您的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} " )並添加${matchit_SOURCE_DIR}/include insubly路徑。
用最新版本的標籤替換main ,以避免API兼容性破壞。
克隆倉庫通過
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添加了支持。)
為了使調試更容易,請嘗試以單獨的行編寫Lambda功能主體,以便您可以在其中設置斷點。
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變體或範圍一樣。
請嘗試創建一個最少的樣本來重現您遇到的問題。您可以以這種方式更快地引起問題。
您還可以在此存儲庫中創建一個問題,並附加最小的示例代碼,我會盡快響應(有時請期望延遲一兩天)。
有關語法設計詳細信息,請參考參考。
Rust到IT(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);我們超載了一些通配符符號的操作員_ ,以促進基本謂詞的使用。
有時我們想分享一個用於多種模式的處理程序,或者是拯救的模式:
# 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)一個簡單的樣本來檢查數字是否大可能是:
# 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 ) 。 (不要將其標記為常量。)如果您使用其他編程語言的標識符模式,這可能會有些奇怪。這是由於語言限制。但是不要難過。這增加了詳細的詳細性使我們有可能在模式內使用變量。您可能永遠無法使用其他編程語言來執行此操作。
這裡*運算符用於取消標識符中的值。要注意的一件事是,標識符僅在match範圍內有效。不要試圖在外面解僱它。
Id::at類似於Rust中的@模式,即當匹配子量時,綁定值。
另請注意,當相同的標識符多次綁定時,綁定值必須通過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 :: begin std::end Supports。
可以省略最外面的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的值。
還有一些方法可以破壞您的結構 /類別,使您的結構 /類元組樣或採用應用程序模式。第二個選項看起來像
// 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說,我們只想在兩個標識符等於某些值的總和時匹配,我們可以將代碼寫為
# 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模式中使用,最多只能在ds模式中出現一個OOO模式。當您想檢查元組模式時,您可以像以下那樣寫代碼。
# 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或其他容器 /範圍時,我們可以將子範圍綁定到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
);在第一個模式中,我們要求頭等於末端。如果是這種情況,我們會通過遞歸調用進一步檢查其餘部分(綁定到子量)。一旦一些嵌套的呼叫無法滿足該要求(落到第二種模式)後,檢查就會失敗。否則,只有一個元素或範圍大小為零時,最後的模式就會匹配,我們將返回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);一些模式接受了一個副本。在樣品中,子圖案是一個標識符,我們將剝離的結果結合到它。沒有模式一個人。
有些和沒有模式不是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 );對於某種模式,首先,我們將值投放為布爾值,如果布爾值為真,我們可以進一步取消它。否則,比賽失敗。對於無模式,我們簡單地檢查轉換的布爾值是否為false。
由於模式對於處理sum type非常有用,包括類層次結構, std::variant和std::any 。 std::variant和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默認使用AS模式,但是我們可以通過自定義點更改行為。用戶可以通過定義其類的get_if函數來自定義下鑄件,類似於std::variant std::get_if :: 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 ;
}還有其他自定義點。
如果用戶想添加全新的模式,則可以專門化PatternTraits 。
要注意的一件事是, 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的80x86彙編器,例如Masm/Nasm。
如果您知道使用此庫的其他項目,請通過提交問題或公關來告訴我。
如果您對圖書館有任何疑問或想法,請打開問題。
歡迎討論 /問題 / PR。
match(it)的語法 /模式設計受這些相關工作的影響很大
如果您對match(it)感興趣,那麼您也可能對HSPP感興趣,將Haskell樣式編程帶到C ++。
請標記回購,分享回購或贊助商一美元,讓我知道這個圖書館很重要。
感謝所有人的貢獻代碼和發送錯誤。
特別是由於以下貢獻者:
Hugo Etchegoyen(@hugoetchegoyen)
感謝 @e-dant贊助該項目。