Introduction
C++ have very powerfull tool - templates. But work with tepmlates contains large amount of boilerplate code, which does not contains actual logic.
For example, we want to make wrapper for jni calls to java functions. Usually it looks like this:
int call_java_helper(int value){
return jni->CallIntMethod( value , ....);
}
float call_java_helper(float value){
return jni->CallFloatMethod( value ,....);
}
void call_java_helper(nullptr_t){
jni->CallVoidMethod(....);
}
template<class T>
auto call_java(T value){
cout << "Start Java Call";
return call_java_helper(value);
}
With pattern mathing on type this would look like this:
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(...); }
);
}
But C++ doesn't have pattern matching mechanism/syntax. Not yet...
So, we offer small (just 50 LOC), fast and feasible technique to use pattern matching with C++14.
Our way of doing pattern matching is somewhat constraint, it is actually rather type matching. But we believe that this should be enough for day-to-day ussage.
Using the code
Examples
Dealing with generic variable (like boost::variant):
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; }
);
}
Dealing with compile-time conditions: 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" ; }
);
Returning 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;
Syntax
match(value
,[](std::string value) { }
,[](int i) { return i+100; }
,[](auto a) { }
);
How it works?
Code as is:
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...);
}
template<class T, class Case>
decltype(auto) match(T&& value, const Case& _case) {
return _case(std::forward<T>(value));
}
match accepts matched value, and list of lambdas (which are "cases"). We except, that lambda accepts only one non generic parameter (except the last one). And with the help of FunctionArgs class we deduct it. Than we just traverse through all cases and found one which have the same type (decayed :) ) as value and call that lambda.
The very last one case/lambda is special case, because we assume that it can be default. So we just call it. Since it is the last one, it will be called only if all previous matces fail. And if it will be non generic, compiler will still throw error if type mismatch (though in this case it will try to cast first).
The reason for this is that we can't distinguish generic/non generic lambda. And FunctionArgs will fail if we pass generic lambda to it.
FunctionArgs is 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...> {};
P.S. If someone know how to detect generic/non generic lambda let me know.
P.P.S.
There is a library https://github.com/solodon4/Mach7 which offers similar (and even greater) capabilities. But we found it too heavy (for our own usage). Plus it is based on macros.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.