Нет нужды описывать чем хорош pattern matching. Так как в любом случае такой конструкции в С++ нет.
Без него же работа с шаблонами, часто обрастает лесами понятного и полезного кода.
Итак предлагаю способ некоего подобия pattern matching`а для С++14 (скорее даже type matching'a), который укладывается в 50 строк кода, не использует макросы и вообще кросс-компиляторный.
Сначала пример использования: http://coliru.stacked-crooked.com/a/6066e8c3d87e31eb
template<class T>
decltype(auto) test(T& value) {
return match(value
,[](std::string value) { cout << "This is string"; return value + " Hi!"; }
,[](int i) { cout << "This is int"; return i * 100; }
,[](auto a) { cout << "This is default";return nullptr; }
);
}
compile-time Условия: http://coliru.stacked-crooked.com/a/ccb13547b04ce6ad
match(true_type{}
,[](bool_constant< T::value == 10 >) { cout << "1" ; }
,[](bool_constant< (T::value == 20 && sizeof...(Args)>4) >) { cout << "2" ; }
);
Возвращаем тип: http://coliru.stacked-crooked.com/a/0a8788d026008b4b
auto t = match(true_type{}
,[](is_same_t<T, int>) -> type_holder<short> { return{}; }
,[](auto) -> type_holder<T> { return{}; }
);
using I = typename decltype(t)::type;
I i = 1000000;
Синтаксис
match(value // <- значение, тип которого сравнивается
,[](std::string value) { /* будет сравниваться с std::string */ }
,[](int i) { /* можно возвращать значения различных типов */ return i+100; }
,[](auto a) { /* Аналог default: в switch */ }
);
Принцип работы
Основная логика:
namespace details {
template<class T, class Case, class ...OtherCases>
decltype(auto) match_call(const Case& _case, T&& value, std::true_type, const OtherCases&...) {
return _case(std::forward<T>(value));
}
template<class T, class Case, class ...OtherCases>
decltype(auto) match_call(const Case& _case, T&& value, std::false_type, const OtherCases&...) {
return match(std::forward<T>(value), other...);
}
}
template<class T, class Case, class ...Cases>
decltype(auto) match(T&& value, const Case& _case, const Cases&... cases) {
using namespace std;
using args = typename FunctionArgs<Case>::args; // <- Это самое интересное место!
using arg = tuple_element_t<0, args>;
using match = is_same<decay_t<arg>, decay_t<T>>;
return details::match_call(_case, std::forward<T>(value), match{}, cases...);
}
// это для default
template<class T, class Case>
decltype(auto) match(T&& value, const Case& _case) {
return _case(std::forward<T>(value));
}
Функция match
принимает на вход сравниваемое значение value
и список лямбд (которые служат case'ми). У каждой лямбды должен быть ровно один аргумент. С помощью FunctionArgs
мы определяем тип этого аргумента. Затем проходим по всем лямбдам и вызываем ту у которой совпадает тип аргумента.
Предполагается что последняя лямбда может содержать generic аргумент. Поэтому тип её аргументов не проверяется. Она просто вызывается. Если она не generic, и тип не совпадает компилятор просто выдаст ошибку (правда предварительно попытается привести к типу).
Можно было бы как то определять generic последняя лямбда или нет, но как это сделать — неизвестно.
FunctionArgs — модифицированная версия http://stackoverflow.com/a/27867127/1559666 :
template <typename T>
struct FunctionArgs : FunctionArgs<decltype(&T::operator())> {};
template <typename R, typename... Args>
struct FunctionArgsBase{
using args = std::tuple<Args...>;
using arity = std::integral_constant<unsigned, sizeof...(Args)>;
using result = R;
};
template <typename R, typename... Args>
struct FunctionArgs<R(*)(Args...)> : FunctionArgsBase<R, Args...> {};
template <typename R, typename C, typename... Args>
struct FunctionArgs<R(C::*)(Args...)> : FunctionArgsBase<R, Args...> {};
template <typename R, typename C, typename... Args>
struct FunctionArgs<R(C::*)(Args...) const> : FunctionArgsBase<R, Args...> {};
P.S.
Должен заметить, что существует также https://github.com/solodon4/Mach7, которая также реализует pattern matching (можно даже сказать что в более полной мере). Но синтаксис, обилие макросов, её объём, и то что на момент написания статьи она находилась в несколько… разобранном состоянии оттолкнули автора в сторону этого велосипеда…
Впрочем, будем надеяться на светлое будущее в лице с++23 а может и с++20 с поддержкой pattern matching'a со стороны языка.
/*
std::string s = "12";
cout << match(s
,[](int& i) { return "int"; }
,[](bool& b) { return "bool"; }
,[](std::string& s) -> auto& { s += " GAV"; return s; }
,[](auto j) { cout << "default one"; return j; }
);
*/
#include <tuple>
template <typename T>
struct FunctionArgs : FunctionArgs<decltype(&T::operator())> {};
template <typename R, typename... Args>
struct FunctionArgsBase{
using args = std::tuple<Args...>;
using arity = std::integral_constant<unsigned, sizeof...(Args)>;
using result = R;
};
template <typename R, typename... Args>
struct FunctionArgs<R(*)(Args...)> : FunctionArgsBase<R, Args...> {};
template <typename R, typename C, typename... Args>
struct FunctionArgs<R(C::*)(Args...)> : FunctionArgsBase<R, Args...> {};
template <typename R, typename C, typename... Args>
struct FunctionArgs<R(C::*)(Args...) const> : FunctionArgsBase<R, Args...> {};
// forward declarations
template<class T, class Case, class ...Cases>
decltype(auto) match(T&& value, const Case& _case, const Cases&... cases);
template<class T, class Case>
decltype(auto) match(T&& value, const Case& _case);
namespace details {
template<class T, class Case, class ...OtherCases>
decltype(auto) match_call(const Case& _case, T&& value, std::true_type, const OtherCases&... other) {
return _case(std::forward<T>(value));
}
template<class T, class Case, class ...OtherCases>
decltype(auto) match_call(const Case& _case, T&& value, std::false_type, const OtherCases&... other) {
return match(std::forward<T>(value), other...);
}
}
template<class T, class Case, class ...Cases>
decltype(auto) match(T&& value, const Case& _case, const Cases&... cases) {
using namespace std;
using args = typename FunctionArgs<Case>::args;
using arg = tuple_element_t<0, args>;
using match = is_same<decay_t<arg>, decay_t<T>>;
return details::match_call(_case, std::forward<T>(value), match{}, cases...);
}
// the last one is default
template<class T, class Case>
decltype(auto) match(T&& value, const Case& _case) {
return _case(std::forward<T>(value));
}
Автор: tower120