من السهل البدء.
مكتبة رأس واحدة.
واجهات برمجة التطبيقات الخالية من الماكرو.
لا يوجد تخصيص ذاكرة كومة.
قابلية النقل: تم اختباره بشكل مستمر تحت Ubuntu و MacOs و Windows باستخدام GCC/Clang/MSVC.
لا تبعيات خارجية.
الموثوقية: خيار التحقق من برنامج التحويل البرمجي الصارم + المطورين + Valgrind.
أنماط قابلة للتأليف.
يمكن للمستخدمين أن يحددوا أنماطهم الخاصة ، إما عن طريق تكوين الأنماط الموجودة ، أو إنشاء أنماط جديدة.
دعم تدمير حاويات تشبه النطاق وتشبه النطاق.
الدعم الجزئي للتعبير المستمر.
matchit.h ما عليك سوى تنزيل ملف header matchit.h ووضعه في دليل تضمين التبعيات.
هذا كل شيء.
يمكنك التنزيل عبر هذا الأمر باش
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 إلى مسار تضمينك.
استبدل main بأحدث علامة إصدار لتجنب كسر توافق API.
استنساخ الريبو عبر
git clone --depth 1 https://github.com/BowenFu/matchit.cpp
تثبيت المكتبة عبر
cd matchit.cpp
cmake -B ./build
cd build
make install
ثم استخدم find_package في cmakelists.txt.
(بفضل @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 أو النطاقات.
يرجى محاولة إنشاء عينة الحد الأدنى لإعادة إنتاج المشكلات التي قابلتها. يمكنك أن تسبب المشكلة بسرعة أكبر بهذه الطريقة.
يمكنك أيضًا إنشاء مشكلة في هذا الريبو وإرفاق الحد الأدنى من رموز العينات وسأحاول الاستجابة في أقرب وقت ممكن (في بعض الأحيان يرجى توقع تأخير يوم أو يومين).
للحصول على تفاصيل تصميم بناء الجملة يرجى الرجوع إلى المرجع.
المستند من الصدأ لتطابق (IT) يعطي عينات مكافئة لعينات الصدأ المقابلة.
هناك قد يكون لديك صورة لما سيكون عليه الترميز مع match(it) .
يعطي المستند من مقترح مطابقة الأنماط مطابقة (IT) عينات مكافئة للعينات المقابلة في اقتراح مطابقة نمط المطابقة.
هناك سترى إيجابيات وسلبيات المكتبة على الاقتراح.
لنبدأ رحلة في المكتبة!
(للحصول على عينات كاملة ، يرجى الرجوع إلى دليل العينات.)
تعرض العينة التالية كيفية تنفيذ LATOPARITIONT باستخدام مكتبة 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,
...
)هذه مكالمة دالة وستعود بعض القيمة التي يتم إرجاعها من قبل المعالجات. نوع الإرجاع هو النوع الشائع لجميع المعالجات. سيكون نوع الإرجاع باطلاً إذا لم يرجع جميع المعالجات القيم. أنواع الإرجاع غير المتوافقة من معالجات متعددة هي خطأ في الترجمة. عندما تُرجع المعالجات القيم ، يجب أن تكون الأنماط شاملة. سيحدث خطأ في وقت التشغيل إذا لم يتم مطابقة جميع الأنماط. إنه ليس خطأ إذا كانت أنواع إرجاع المعالجات كلها باطلة.
يمكن أن يكون المعالج أيضًا قيمة أو متغير معرف. 1 يعادل []{return 1;} .
سوف يتطابق Wildcard _ إلى أي قيم. إنها ممارسة شائعة استخدامها دائمًا كنمط آخر ، ولعب نفس الدور في مكتبتنا كما تفعل default case لبيانات switch ، لتجنب الهروب من الحالة.
يمكننا مطابقة قيم متعددة في نفس الوقت:
# 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 ) أولاً . (لا تضع علامة عليها على أنها const.) يمكن أن يكون هذا غريبًا بعض الشيء إذا كنت تستخدم أنماط المعرف في لغة البرمجة الأخرى. هذا بسبب تقييد اللغة. لكن لا تنزعج. هذا التصرف المضافة يجعل من الممكن بالنسبة لنا استخدام المتغيرات داخل الأنماط . قد لا تتمكن أبدًا من القيام بذلك بلغة برمجة أخرى.
هنا * يتم استخدام المشغل لإرهاق القيمة داخل المعرفات. شيء واحد يجب ملاحظته هو أن المعرفات صالحة فقط داخل نطاق match . لا تحاول أن تخطر عليه بالخارج.
Id::at يشبه نمط @ في الصدأ ، أي ربط القيمة عند مطابقة subpattern.
لاحظ أيضًا عندما يكون المعرف نفسه ملزمًا عدة مرات ، يجب أن تساوي القيم المرتبطة بعضها البعض عبر 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 supporms.
يمكن حذف أقصى 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 . يمكنك كتابة الرمز على النحو التالي عندما تريد التحقق من نمط tuple.
# 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); ما هو أكثر من ذلك ، يمكننا ربط ترتيب فرعي لنمط OOO عند تدمير std::array أو حاويات / نطاقات أخرى. هذا رائع جدا. يمكننا التحقق مما إذا كان/a/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
);في النمط الأول ، نطلب أن يكون الرأس متساويًا حتى النهاية. وإذا كان هذا هو الحال ، فإننا نتحقق من أجزاء الراحة (المرتبطة بالترتيب الفرعي) عبر مكالمة متكررة. بمجرد فشل بعض المكالمة المتداخلة في تلبية هذا المطلب (تصل إلى النمط الثاني) ، فشل التحقق. وإلا عندما يكون هناك عنصر واحد فقط أو يكون حجم النطاق صفرًا ، يتم مطابقة النمط الأخير ، نعود صحيحًا.
لقد فعلنا مع أنماطنا الأساسية. الآن دعنا نبدأ رحلة تأليف الأنماط .
يجب أن تكون على دراية ببعض الأنماط ولا يوجد نمط إذا كنت قد استخدمت ميزة مطابقة الأنماط في الصدأ.
يمكن استخدام بعض / لا شيء أنماط لمطابقة المؤشرات الأولية ، std::optional ، std::unique_ptr ، std::shared_ptr وأنواع أخرى يمكن تحويلها إلى bool و dereference. يمكن أن تكون عينة نموذجية
# 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. في العينة ، يكون 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 );بالنسبة لبعض الأنماط ، أولاً ، نقوم بإلقاء القيمة على قيمة منطقية ، إذا كانت القيمة المنطقية صحيحة ، فيمكننا مزيد من إشرافها. خلاف ذلك ، تفشل المباراة. بالنسبة إلى أي نمط ، نحن ببساطة نتحقق مما إذا كانت القيمة المنطقية المحولة خاطئة.
نظرًا لأن النمط مفيد جدًا لمعالجة 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 بشكل افتراضي لنمط ، ولكن يمكننا تغيير السلوك من خلال نقطة التخصيص . يمكن للمستخدمين تخصيص الصب لأسفل من خلال تحديد وظيفة get_if لفئاتهم ، على غرار std::get_if for 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 ;
}هناك نقطة تخصيص إضافية.
يمكن للمستخدمين متخصصين 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: تجميع 80x86 مثل Masm/Nasm لنظام التشغيل الصغير.
إذا كنت على دراية بالمشاريع الأخرى التي تستخدم هذه المكتبة ، فيرجى إخبارنا عن طريق تقديم مشكلة أو علاقات عامة.
إذا كانت لديك أي أسئلة أو أفكار تتعلق بالمكتبة ، فيرجى فتح مشكلة.
المناقشات / القضايا / PRS كلها موضع ترحيب.
تم تأثر تصميمات بناء الجملة / النمط match(it) بشكل كبير بهذه الأعمال ذات الصلة
إذا كنت مهتمًا match(it) ، فقد تكون مهتمًا أيضًا بـ HSPP الذي يجلب برمجة نمط Haskell إلى C ++.
يرجى دور البطولة ، مشاركة الريبو ، أو رعاية دولار واحد لإخبارنا بأن هذه المكتبة مهمة.
شكرا للجميع على الكود المساهمين وإرسال الأخطاء.
على وجه الخصوص ، بفضل المساهمين التاليين:
Hugo etchegoyen (hugoetchegoyen)
بفضل @e-dant لرعاية هذا المشروع.