There is no need to describe what pattern matching is good for. Since in any case there is no such construction in C ++.
Without it, working with templates often becomes overgrown with forests of clear and useful code.
So, I suggest a method of some similarity of pattern matching for C ++ 14 (rather, even type matching), which fits into 50 lines of code, does not use macros, and generally cross-compiler.
First example of use: 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 Terms: 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" ; } );
Return the type: 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;
For example, you write a wrapper for calling a java function from C ++ (via jni).
Usually it would look like:
int call_java_helper(int element){ return jni->CallIntMethod(....); } float call_java_helper(float element){ return jni->CallFloatMethod(....); } void call_java_helper(nullptr_t){ jni->CallVoidMethod(....); } template<class T> auto call_java(T element){ cout << "Start Java Call"; return call_java_helper(element); }
Using pattern matching:
template<class T> auto call_java(T element){ cout << "Start Java Call"; return match(elment, ,[](int element) { return jni->CallIntMethod(element); } ,[](float element){ return jni->CallFloatMethod(element); } ,[](auto) { jni->CallVoidMethod(); } ); }
Everything is collected in one place.
I think you can draw some kind of analogy with the Rust pattern matching by enum
match(value // <- , ,[](std::string value) { /* std::string */ } ,[](int i) { /* */ return i+100; } ,[](auto a) { /* default: switch */ } );
Basic logic:
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)); }
The match
function accepts a comparable value
and a list of lambdas (which serve as cases) as input. Each lambda should have exactly one argument. Using FunctionArgs
we define the type of this argument. Then we go through all the lambdas and call the one that matches the type of the argument.
It is assumed that the last lambda may contain a generic argument. Therefore, the type of its arguments is not checked. She just called. If it is not generic, and the type does not match, the compiler will simply generate an error (although it will first try to convert to type).
It would be possible to somehow define the generic last lambda or not, but how to do this is unknown.
FunctionArgs is a modified version of 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...> {};
I should note that there is also https://github.com/solodon4/Mach7 , which also implements pattern matching (you can even say that more fully). But the syntax, the abundance of macros, its volume, and the fact that at the time of this writing it was in a bit ... exploded state pushed the author towards this bike ...
However, let's hope for a bright future in the face of c ++ 23 and maybe c ++ 20 with support for pattern matching from the language.
/* 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)); }
Update
The comments offered in my opinion a more perfect way https://habrahabr.ru/post/282630/#comment_8873766 .
With it, you can make a comparison with the sample at once on several values.
Even if you are doing a mapping of just one value, you may need to simply pass additional arguments to the function. For example, in the following example, in clang you need to pass type to a function (gcc and VS work without it):
template<class Out, class ...Args> inline Out run(Args&&...args){ auto in = std::tie(std::forward<Args>(args)...); return match(type_holder<Out>() ,[&](type_holder<void>){ command(Parcel(in), Parcel()); } ,[&](auto type)->Out{ Out out; // clang : void . //typename decltype(type)::type out; // command(Parcel(in), Parcel(out)); return out; } ); }
But something in the spirit:
match( [&](false_type, auto) { command(); }, [&](true_type, auto type) { typename decltype(type)::type out; command(Parcel(out)); } )(is_void{}, type_holder<Out>{});
It is impossible to do.
If the sample is not found, the compiler produces quite clear messages.
http://coliru.stacked-crooked.com/a/1f3723d422ef05ee
namespace detail { template <class ...> struct match; template <class Head, class ... Tail> struct match<Head, Tail...> : match<Tail...>, Head { template <class Head2, class ... Tail2> match(Head2&& head, Tail2&& ... tail) : match<Tail...>(std::forward<Tail2>(tail)...), Head(std::forward<Head2>(head)) {} using Head::operator(); using match<Tail...>::operator(); }; template <class T> struct match<T> : T { template <class R> match(R&& r) : T(std::forward<R>(r)) {} using T::operator(); }; } template <class ... Cases> auto match(Cases&& ... cases) { return detail::match<typename std::decay<Cases>::type...>{std::forward<Cases>(cases)...}; } int main() { int io = 100; int i = 1000; match( [](int i1 , auto i2) { cout << "int" << i1 << " " << i2; } ,[](short, char) { cout << "short"; } ,[&](auto...) { cout << "auto " << io; } )(1, i); return 0; }
PS How much I did not fight, could not bring the syntax to match(1,2) ( cases...)
so that the designer was not called move http://coliru.stacked-crooked.com/a/70d1aec24c26642a
Source: https://habr.com/ru/post/282630/
All Articles