Click here to Skip to main content
15,881,139 members
Articles / Programming Languages / C++

C++ pattern matching (type matching)

Rate me:
Please Sign up or sign in to vote.
3.88/5 (10 votes)
1 May 2016CPOL2 min read 22.8K   13   5
simple pattern matching technique for modern C++

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:

C++
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:

C++
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

C++
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

C++
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

C++
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                    // <- value of matched type
     ,[](std::string value)    { /* std::string is type to match */ }         
     ,[](int i)                { /* we can return value of different types */ return i+100; } 
     ,[](auto a)               { /* If everything else fail this will be called */ }     
); 

How it works?

Code as is:

C++
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;               // <- A little bit of magic here!
    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));
}

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 :

C++
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Ukraine Ukraine
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionSomewhat misleading title. Pin
Loic URIEN1-May-16 3:13
Loic URIEN1-May-16 3:13 
While I think the article content is ok, the title is terribly misleading ! Pattern matching is far more powerful than this simple type matching, which is basically a lazy way to do overloading to me. However, you seem to have partical reasons to do this that way, so this article is still fair, but I can not help but feel that the title will make others than me disappointed by the content Big Grin | :-D

Thanks in advance for taking in account my comment !
AnswerRe: Somewhat misleading title. Pin
tower1201-May-16 9:53
tower1201-May-16 9:53 
GeneralRe: Somewhat misleading title. Pin
Loic URIEN1-May-16 10:11
Loic URIEN1-May-16 10:11 
QuestionC+ Pin
DavidSWashington28-Apr-16 19:37
DavidSWashington28-Apr-16 19:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.