Search code examples
c++variadic-templatestemplate-meta-programmingparameter-pack

How can i combine multiple variadic templates or split parameter pack?


I am currently trying to define a generic multiplication operator for std::functions. I want to do this using multiple variadic templates. The partial specializations look as follows:

template <typename... _Type> using op_Type = typename std::function<u64(u64, _Type...)>;

inline auto operator*(const op_Type<>& f, const op_Type<>& g) -> op_Type<> {
    return [f, g](u64 num) {
        return f(g(num));
    };
}
template <typename _Ty1, typename _Ty2>
inline auto operator*(const op_Type<_Ty1>& f, const typename op_Type<_Ty2>& g)
-> op_Type<_Ty1, _Ty2> {
    return [f, g](u64 num, _Ty1 arg1, _Ty2 arg2) {
        return f(g(num, arg2), arg1);
    };
}
template <typename _Tyf1, typename _Tyf2, typename _Tyg1, typename _Tyg2>
inline auto operator*(const op_Type<_Tyf1, _Tyf2>& f,
    const typename op_Type<_Tyg1, _Tyg2>& g)
    -> op_Type<_Tyf1, _Tyf2, _Tyg1, _Tyg2> {
    return [f, g](u64 num, _Tyf1 argF1, _Tyf2 argF2, _Tyg1 argG1, _Tyg2 argG2) {
        return f(g(num, argG1, argG2), argF1, argF2);
    };
}

But what I would need is the same behavior for any generic std::functions taking an u64 value and any number of other arguments, which should look as follows:

template <typename... _Ty1, template <typename...> class op1 
        , typename... _Ty2, template <typename...> class op2
        , typename... _RetTy, template<typename...> class opRet>
inline auto operator*(const op1<_Ty1...>& f, const op2<_Ty2...> g) -> opRet<_RetTy...> {
    const int size1 = sizeof...(_Ty1);
    const int size2 = sizeof...(_Ty2);
    return [f, g](u64 num, _Ty1 args1..., _Ty2 args2...) {
        auto tmp = g(num, std::forward<_Ty1>(args1)...);
        return f(tmp, std::forward<_Ty2>(args2)...);
    };
}

I also would want to drop the added class templates, but it might not be possible then to use multiple variadic templates, because the compiler don't know when the variadic template ends, right? I suppose a good workaround would be to split the parameter pack such that:

template <typename... _Ty1, , typename... _Ty2>
inline auto operator*(const op_type<_Ty1...>& f, const op_type<_Ty2...> g) -> opRet<_Ty1...,_Ty2...> {
    const int size1 = sizeof...(_Ty1);
    const int size2 = sizeof...(_Ty2);
    return [f, g](u64 num, variadic template args...) {
        auto tmp = g(num, split_args(args, 0, size1 - 1));
        return f(tmp, split_args(args, remaining_arguments);
    };
}

where split_args returns the arguments between the input indices, but I'm not sure how to implement that, any ideas? I found a solution like this: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0535r0.html but I'm not sure if there is an open source code available

EDIT: IN conclusion I would need a function which looks like:

template <typename... _Ty1, typename... _Ty2>
   inline auto operator*(const op_type<_Ty1...>& f, const op_type<_Ty2...> g) -> op_type<_Ty1...,_Ty2...> {
        return [f, g](u64 num, _Ty1 args1..., _Ty2 args2...) {
            auto tmp = g(num, std::forward<_Ty1>(args1)...);
            return f(tmp, std::forward<_Ty2>(args2)...);
        };
    }

EDIT2: Usage: Suppose i have two functions:

auto f(u64 num, int i, int j) -> u64{
     return num + (i - j)^i;
}

and

auto g(u64 num, double x){
     return num - int(num / x);
}

then the multiplication operator h = f*g should return h as:

auto h(u64 num, int i, int j, double x) -> u64{
     num + (i - j)^i - int(num / x);
}

Solution

  • In hope I got your wishes...

    
    template< typename PACK1, typename PACK2 > struct Combined;
    
    template < typename ... PACK1, typename ... PACK2 >
    struct Combined< std::function< uint64_t( uint64_t, PACK1... )>, std::function< uint64_t(uint64_t, PACK2...) > >
    {
        using OP_TYPE1 = std::function< uint64_t( uint64_t, PACK1... )>;
        using OP_TYPE2 = std::function< uint64_t( uint64_t, PACK2... )>;
    
        OP_TYPE1 op1;
        OP_TYPE2 op2;
        Combined( OP_TYPE1 op1_, OP_TYPE2 op2_ ): op1{ op1_}, op2{ op2_}{}
    
        auto operator( )(uint64_t p1, PACK1... args1, PACK2... args2)
        {
            return op2( op1( p1, args1...), args2...);
        }
    };
    
    
    template < typename OP_TYPE1, typename OP_TYPE2> auto operator*(OP_TYPE1, OP_TYPE2);
    
    template < typename ... PACK1, typename ... PACK2 >
    auto operator* ( std::function< uint64_t( uint64_t, PACK1... )> op1, std::function< uint64_t(uint64_t, PACK2...) > op2 )
    {
        return Combined< std::function< uint64_t( uint64_t, PACK1... )>, std::function< uint64_t(uint64_t, PACK2...)>>{ op1, op2 };
    }
    
    // Example funcs
    auto f(uint64_t num, int i, int j) -> uint64_t{
         return num + ((i - j)^i);
    }
    
    uint64_t g(uint64_t num, double x){
         return num - int(num / x);
    }
    
    
    int main()
    {
        std::function fobject = f;
        std::function gobject = g;
    
        auto fg = fobject*gobject;
    
        std::cout << fg( 1, 2, 3, 6.66 ) << std::endl;
    }
    

    The exmaple misses all what can be optimized with forwarding arguments, moving and so on. It is only for you to catch signatures and taking params from template arguments and so on.