시작하기 쉽습니다.
단일 헤더 라이브러리.
매크로가없는 API.
힙 메모리 할당이 없습니다.
이식성 : GCC/Clang/MSVC를 사용하여 Ubuntu, MacOS 및 Windows에서 지속적으로 테스트했습니다.
외부 의존성이 없습니다.
신뢰성 : 엄격한 컴파일러 점검 옵션 + 소독제 + valgrind.
합성 가능한 패턴.
확장 가능한 사용자는 존재하는 패턴을 통해 자신의 패턴을 정의하거나 새로운 패턴을 만들 수 있습니다.
튜플과 같은 범위와 같은 용기 파괴를 지원합니다.
일정한 표현에 대한 부분 지원.
matchit.h 헤더 파일 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
이제 도서관은 Conan Center Index에 제출되었습니다.
이제 Conan을 통해 라이브러리를 설치할 수 있습니다.
(지원을 추가 한 @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 변형 또는 범위로 디버깅하지 않는 것처럼이 라이브러리의 일부 버그를 근본 원인 / 수정하기로 결정하지 않는 한이 라이브러리로 디버깅하지 마십시오.
충족 한 문제를 재현하기 위해 최소한의 샘플을 만들어보십시오. 그런 식으로 문제를 더 빨리 원인을 근본 할 수 있습니다.
이 repo에서 문제를 생성하고 최소한의 샘플 코드를 첨부 할 수 있으며 가능한 빨리 응답을 시도하겠습니다 (때로는 1 ~ 이틀 지연이 지연 될 예정입니다).
구문 디자인 세부 사항은 참조를 참조하십시오.
Rust to Match (IT)의 문서는 해당 녹슬 샘플과 동등한 샘플을 제공합니다.
거기에서 match(it) 로 코딩하는 사진이있을 수 있습니다.
패턴 매칭 제안서에 대한 문서 (IT)는 매치 패턴 매칭 제안에서 해당 샘플에 동등한 샘플을 제공합니다.
거기에서 제안서에 대한 도서관의 장단점을 볼 수 있습니다.
도서관에서 여행을 시작합시다!
(전체 샘플은 샘플 디렉토리를 참조하십시오.)
다음 샘플은 match(it) 라이브러리를 사용하여 Factorial을 구현하는 방법을 보여줍니다.
# 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)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 )를 정의/선언해야합니다 . (다른 프로그래밍 언어에서 식별자 패턴을 사용하는 경우 이것은 약간 이상 할 수 있습니다. 이것은 언어 제한 때문입니다. 그러나 화를 내지 마십시오. 이 추가 된 진실성으로 인해 패턴 내부의 변수를 사용할 수 있습니다. 다른 프로그래밍 언어로는이 작업을 수행 할 수 없습니다.
여기서 * 운영자는 내부 식별자 내부의 값을 해석하는 데 사용됩니다. 주목할만한 점은 식별자가 match 범위 내에서만 유효하다는 것입니다. 외부에서 그것을 피하려고 시도하지 마십시오.
Id::at Rust의 @ Pattern 과 유사합니다. 즉, 하위 패턴이 일치 할 때 값을 바인딩합니다.
또한 동일한 식별자가 여러 번 제한되면 결절 값이 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::end 에 사용될 수 있습니다 std::begin
가장 바깥 쪽 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 패턴 에옵니다. 저게 뭐에요? 당신은 물어볼 수 있습니다. 일부 프로그래밍 언어에서는 휴식 패턴 이라고합니다. 임의의 품목과 일치 할 수 있습니다. ds 패턴 내에서만 사용할 수 있으며 최대 하나의 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 또는 기타 컨테이너 / 범위를 파괴 할 때 서브 랜지를 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
);첫 번째 패턴에서는 머리가 끝까지 동일해야합니다. 그리고이 경우, 우리는 재귀 통화를 통해 나머지 부분 (서브 랜지에 바인딩)을 추가로 확인합니다. 일부 중첩 된 통화가 해당 요구 사항을 충족하지 못하면 (두 번째 패턴에 빠지면) 점검이 실패합니다. 그렇지 않으면 왼쪽이 하나만 있거나 범위 크기가 0 인 경우 마지막 패턴이 일치하면 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 );어떤 패턴의 경우, 먼저 우리는 부울 값으로 값을 부울 값으로 시전합니다. 부울 값이 사실이라면 더 이상 연기 할 수 있습니다. 그렇지 않으면 경기가 실패합니다. 패턴 없음에 대해 변환 된 부울 값이 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 기본적으로 AS 패턴으로 사용되지만 사용자 정의 지점을 통해 동작을 변경할 수 있습니다. 사용자는 std std::variant std::get_if 와 유사한 클래스의 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 ;
}추가 CustomZiation 지점이 있습니다.
사용자는 새로운 패턴을 추가하려면 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에 대한 masm/nasm과 같은 80x86 어셈블러.
이 라이브러리를 사용하는 다른 프로젝트를 알고 있다면 문제 나 PR을 제출하여 알려주십시오.
도서관에 관한 질문이나 아이디어가 있으면 문제를여십시오.
토론 / 문제 / PR은 모두 환영합니다.
match(it) 의 구문 / 패턴 디자인은 이러한 관련 작업에 크게 영향을 받았습니다.
match(it) 에 관심이 있으시면 Haskell 스타일 프로그래밍을 C ++에 가져 오는 HSPP에도 관심이있을 수 있습니다.
이 라이브러리 문제를 알려주기 위해 저장소를 출연하거나 리포지션을 공유하거나 1 달러를 후원하십시오.
코드를 기여하고 버그를 보내 주셔서 감사합니다.
특히 다음 기고자들에게 감사합니다.
Hugo Etchegoyen (@Hugoetchegoyen)
이 프로젝트를 후원 해 주신 @e-dant에게 감사드립니다.