Search code examples
c++c++11stlstd-functionstdbind

How To Combine Multiple std::function To One?


I have multiple std::function. Each of them have different input and output, and the input of one std::function might be the output of another std::function, which means that "serial" convert from one to another.

Maybe I can't describe it clear enough. Let code talks

std::function<bool(double)> combine(std::function<int(double)> convert1
                                    , std::function<char(int)> convert2
                                    , std::function<bool(char)> convert3)
{
    return std::bind(convert1, convert2, convert3)//error. A function directly convert [double] to [bool] using convert1, convert2, convert3
}

Here is very simple code which I already remove the pointless code and show the core of my meaning.

So you can see convert1 do conversion from double to int, convert2 do conversion from int to char and convert3 do conversion from char to bool. Now I need to combine them together and so that I can directly convert double to bool.

And you know, I am not really want to convert double to bool. It's only for test.

One option to implement this is that write a help function:

bool helper(double d
            , std::function<int(double)> convert1
            , std::function<char(int)> convert2
            , std::function<bool(char)> convert3)
{
    return convert3(convert2(convert1(d)));
}

std::function<double(bool)> combine(std::function<int(double)> convert1
                                    , std::function<char(int)> convert2
                                    , std::function<bool(char)> convert3)
{
    return helper;
}

But it's ugly code and maybe I use this conversion in a common way, which means that I should write this helper for all kind of my conversion.

So, is there a directly way to combine these function together?


Solution

  • Creating a simple type traits to extract the input type of the last function

    template <typename, typename...>
    struct lastFnType;
    
    template <typename F0, typename F1, typename ... Fn>
    struct lastFnType<F0, F1, Fn...>
     { using type = typename lastFnType<F1, Fn...>::type; };
    
    template <typename T1, typename T2>
    struct lastFnType<std::function<T2(T1)>>
     { using type = T1; };
    

    you can transform the apple apple's solution (+1) in a more general variadic template recursive solution

    template <typename T1, typename T2>
    std::function<T1(T2)> combine (std::function<T1(T2)> conv)
     { return conv; }
    
    template <typename T1, typename T2, typename T3, typename ... Fn>
    std::function<T1(typename lastFnType<std::function<T2(T3)>, Fn...>::type)>
        combine (std::function<T1(T2)> conv1, std::function<T2(T3)> conv2, 
                 Fn ... fn)
     {
       using In = typename lastFnType<std::function<T2(T3)>, Fn...>::type;
    
       return [=](In const & in){ return conv1(combine(conv2, fn...)(in)); };
     }
    

    But observe that the order of the converter is inverted (call with last used converter first; so combine(convert3, convert2, convert1))

    The following is a full example

    #include <functional>
    
    template <typename, typename...>
    struct lastFnType;
    
    template <typename F0, typename F1, typename ... Fn>
    struct lastFnType<F0, F1, Fn...>
     { using type = typename lastFnType<F1, Fn...>::type; };
    
    template <typename T1, typename T2>
    struct lastFnType<std::function<T2(T1)>>
     { using type = T1; };
    
    template <typename T1, typename T2>
    std::function<T1(T2)> combine (std::function<T1(T2)> conv)
     { return conv; }
    
    template <typename T1, typename T2, typename T3, typename ... Fn>
    std::function<T1(typename lastFnType<std::function<T2(T3)>, Fn...>::type)>
        combine (std::function<T1(T2)> conv1, std::function<T2(T3)> conv2, 
                 Fn ... fn)
     {
       using In = typename lastFnType<std::function<T2(T3)>, Fn...>::type;
    
       return [=](In const & in){ return conv1(combine(conv2, fn...)(in)); };
     }
    
    int fn1 (double d)
     { return d*2.0; }
    
    char fn2 (int i)
     { return i+3; }
    
    bool fn3 (char c)
     { return c == 'a'; }
    
    
    int main ()
     {
       std::function<int(double)> f1 { fn1 };
       std::function<char(int)> f2 { fn2 };
       std::function<bool(char)> f3 { fn3 };
    
       auto cmb = combine(f3, f2, f1);
    
       bool b { cmb(3.2) };
     }