C++ pattern matching

Нет нужды описывать чем хорош pattern matching. Так как в любом случае такой конструкции в С++ нет.
Без него же работа с шаблонами, часто обрастает лесами понятного и полезного кода.
Итак предлагаю способ некоего подобия pattern matching`а для С++14 (скорее даже type matching’a), который укладывается в 50 строк кода, не использует макросы и вообще кросс-компиляторный.

Сначала пример использования: http://coliru.stacked-crooked.com/a/6066e8c3d87e31eb

template
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) -> type_holder  { return{}; }
           ,[](auto)              -> type_holder      { 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
    decltype(auto) match_call(const Case& _case, T&& value, std::true_type, const OtherCases&...) {
        return _case(std::forward(value));
    }

    template
    decltype(auto) match_call(const Case& _case, T&& value, std::false_type, const OtherCases&...) {
        return match(std::forward(value), other...);
    }
}

template
decltype(auto) match(T&& value, const Case& _case, const Cases&... cases) {
    using namespace std;
    using args = typename FunctionArgs::args;               // <- Это самое интересное место!
    using arg = tuple_element_t<0, args>;
    using match = is_same, decay_t>;
    return details::match_call(_case, std::forward(value), match{}, cases...);
}

// это для default
template
decltype(auto) match(T&& value, const Case& _case) {
    return _case(std::forward(value));
}

Функция match принимает на вход сравниваемое значение value и список лямбд (которые служат case’ми). У каждой лямбды должен быть ровно один аргумент. С помощью FunctionArgs мы определяем тип этого аргумента. Затем проходим по всем лямбдам и вызываем ту у которой совпадает тип аргумента.

Предполагается что последняя лямбда может содержать generic аргумент. Поэтому тип её аргументов не проверяется. Она просто вызывается. Если она не generic, и тип не совпадает компилятор просто выдаст ошибку (правда предварительно попытается привести к типу).

Можно было бы как то определять generic последняя лямбда или нет, но как это сделать — неизвестно.

FunctionArgs — модифицированная версия http://stackoverflow.com/a/27867127/1559666:

template 
struct FunctionArgs : FunctionArgs {};

template 
struct FunctionArgsBase{
    using args  = std::tuple;
    using arity = std::integral_constant;
    using result = R;
};

template 
struct FunctionArgs : FunctionArgsBase {};
template 
struct FunctionArgs : FunctionArgsBase {};
template 
struct FunctionArgs : FunctionArgsBase {};

Должен заметить, что существует также 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 

template 
struct FunctionArgs : FunctionArgs {};

template 
struct FunctionArgsBase{
    using args  = std::tuple;
    using arity = std::integral_constant;
    using result = R;
};

template 
struct FunctionArgs : FunctionArgsBase {};
template 
struct FunctionArgs : FunctionArgsBase {};
template 
struct FunctionArgs : FunctionArgsBase {};

// forward declarations
template
decltype(auto) match(T&& value, const Case& _case, const Cases&... cases);
template
decltype(auto) match(T&& value, const Case& _case);

namespace details {
    template
    decltype(auto) match_call(const Case& _case, T&& value, std::true_type, const OtherCases&... other) {
        return _case(std::forward(value));
    }

    template
    decltype(auto) match_call(const Case& _case, T&& value, std::false_type, const OtherCases&... other) {
        return match(std::forward(value), other...);
    }
}

template
decltype(auto) match(T&& value, const Case& _case, const Cases&... cases) {
    using namespace std;
    using args = typename FunctionArgs::args;
    using arg = tuple_element_t<0, args>;
    using match = is_same, decay_t>;
    return details::match_call(_case, std::forward(value), match{}, cases...);
}

// the last one is default
template
decltype(auto) match(T&& value, const Case& _case) {
    return _case(std::forward(value));
}

© Habrahabr.ru