-1

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);
}
Qant123
  • 141
  • 1
  • 6
  • 1
    Can you explain what you really want to do? I see a lot of code but I did not catch what it should do! – Klaus Nov 27 '21 at 17:55
  • I want to overload the multiplication operator taking two std::functions, where each can take any number of parameters (thus variadic template). So in general i need a function defined on two distinct variadic templates, the input arguments to the function are two std::functions (here defined via op_type) with differend variadic templates and return a function combining these variadic templates – Qant123 Nov 27 '21 at 19:33
  • Q1: Both std::function should be "loaded" with the same function? Q2: Combining means, that you want to return a std::function which is "loaded" with the same function object as given by one, maybe the first parameter of your operator and return this one back with the list of both parameters? That means, your functional object must be able to generate a functional object itself, right? Can you please show the function object and the usage in your example please? – Klaus Nov 28 '21 at 08:57
  • And if the first parm to the operator object is always uint64_t, why the others are different? – Klaus Nov 28 '21 at 09:01
  • A1: No the point is that the functions can be different; A2: By combining I mean that if one std::function take (u64,int,int) and the other one takes (u64, double) the resulting std::function from the multiplication should take (u64, int, int, double), so yeah a liast of both parameters where the u64 is common so i don't need to repeat it; As an example for usage I'll edit the question – Qant123 Nov 28 '21 at 09:02
  • I already understood that the parameter list is the sum of all the input parms. But which function should be loaded into the std::function by generating the new combined object? It is not only the std::function type, it must be loeaded with a function object, means something which has the operator(). Somewhere in the code you must have a "std::function<...> x=...???...; The question is, what should ??? be? – Klaus Nov 28 '21 at 09:04
  • Ah! The only intend is to pass the result of calling the first op to the second operator as first parameter, right? – Klaus Nov 28 '21 at 09:13
  • exactly, that's what the operator* should do. It should return a std::function with such behavior. I should have been more clear, sorry – Qant123 Nov 28 '21 at 09:16
  • It should return a functional object, which behaves like calling f(g(...),...); ? – Klaus Nov 28 '21 at 09:18
  • yes exactly, the only reason i want to overload the multiplication is that I can have a lot of different functions to multiply (and also because of better readability) – Qant123 Nov 28 '21 at 09:20
  • So my only problem with the above example is either splitting the parameter pack to separate the arguments to f and g or some other structure, which I am probably not aware of – Qant123 Nov 28 '21 at 09:21
  • `should return h as:` shouldn't it be `auto h(u64 num, int i, int j, double x) -> u64 { return g(f(num, i, j), x); }`? Why did it got inlined? In your code you do `f(g(...), ..)`, the other way round. – KamilCuk Nov 28 '21 at 09:32
  • @KamulCuk I already combined the implementation, that was not a good idea, I meant to have the behavior as you have written with g(f(num,i,j)x);. Also the order of multiplication does not matter for now (i can change f with g in the implementation later) – Qant123 Nov 28 '21 at 09:43

1 Answers1

0

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.

Klaus
  • 24,205
  • 7
  • 58
  • 113
  • does this actually compile and work? I know I had problems with two different parameter packs in the function call as you have in this line: auto operator( )(uint64_t p1, PACK1... args1, PACK2... args2). I suppose this is a C++20 feature, because also the fold expression : op2( op1( p1, args1...), args2...) triggered a compiler warnging in my case, where I use C++17 – Qant123 Nov 28 '21 at 10:03
  • Oh! I see. Because of the forward declarations without a parameter pack the compiler knows where each of the variadic templates should end. Did not know that. That works fine. Thank you very much!! – Qant123 Nov 28 '21 at 10:18