Легко начать.
Библиотека единого заголовка.
Без макро-аПИС.
Нет распределения памяти кучи.
Портативность: непрерывно протестирована под Ubuntu, MacOS и Windows с использованием GCC/Clang/MSVC.
Нет внешних зависимостей.
Надежность: строгая опция проверки компилятора + дезинфицирующие средства + 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 в свой путь включения.
Замените 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) дает эквивалентные образцы для соответствующих выборок в предложении соответствия шаблонов соответствия.
Там вы увидите плюсы и минусы библиотеки из -за предложения.
Давайте начнем путешествие по библиотеке!
(Для получения полных образцов обратитесь к каталогу образцов.)
Следующий образец показывает, как реализовать библиотеку 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;} .
Вильта _ будет соответствовать любым значениям. Это обычная практика, чтобы всегда использовать его в качестве последнего шаблона, играя ту же роль в нашей библиотеке, что и 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); Обратите внимание, что некоторые шаблоны поддерживают матч contexpr, то есть вы можете сопоставить их во время компиляции. Из приведенных выше фрагментов кода мы видим, что 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 аналогична рисунку @ in 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::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 .
Теперь мы приходим к шаблону Ооо . Что это такое? Вы можете спросить. На некотором языке программирования это называется шаблоном отдыха . Вы можете сопоставить произвольное количество элементов с ним. Он может использоваться только внутри шаблонов 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); Более того, мы можем привязать субстанцию с шаблоном OOO при уничтожении std::array или другие контейнеры / диапазоны. Это довольно круто. Мы можем проверить, является ли AS/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 и Dereferenced. Типичный образец может быть
# 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 AS Pattern, но мы можем изменить поведение через точку настройки . Пользователи могут настроить падение вниз, определяя функцию get_if для своих классов, аналогично std::get_if для 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 для крошечной ОС.
Если вам известно о других проектах, использующих эту библиотеку, дайте мне знать, отправив проблему или PR.
Если у вас есть какие -либо вопросы или идеи, касающиеся библиотеки, откройте проблему.
Обсуждения / проблемы / PRS приветствуются.
Синтаксис / рисунок match(it) S -синтаксиса оказали сильное влияние на эту связанную работу
Если вы заинтересованы в match(it) , вы также можете быть заинтересованы в HSPP, который приносит программирование в стиле Haskell в C ++.
Пожалуйста, снимайте репо, поделитесь репо и спонсируйте один доллар, чтобы сообщить мне, что эта библиотека имеет значение.
Спасибо всем за внесение кода и отправку в ошибках.
В частности, благодаря следующим участникам:
Hugo etchegoyen (@hugoetchegoyen)
Спасибо @e-dant за спонсирование этого проекта.