Search code examples
c++templatestemplate-argument-deduction

How can I do the type deduction on parameterized template function


#include <iostream>

template <typename... Ts> void Print(Ts... args) { 
  (std::cout << ... << args) << std::endl;
}

template <typename T> void Add(T a, T b) { Print(a + b); } 

template <typename T> void Sub(T a, T b) { Print(a - b); } 

template <typename T> void Mul(T a, T b) { Print(a * b); } 

template <typename F, typename... Fns> void CallFuncs(F a, F b, Fns... fns) { 
  (fns(a, b), ...);
};

void FunctionInvokeTest() { CallFuncs(1, 2, Add<int>, Mul<int>); }

int main() { 
  FunctionInvokeTest();
  return 0;
}

I want to pass the template function as parameter shown as above. The code works. However I must put <int> after the function such as Add<int>.

If this is non-deductible context, then is there another way to allow me write like this, where the Add and Mul are still template functions?

CallFuncs(1,2, Add, Mul);

Solution

  • You can't do it directly, but you can turn functions into function objects:

    struct Add {
        template<typename T>
        void operator()(T a, T b) {
            Print(a + b); } 
    };
    
    struct Mul {
        template<typename T>
        void operator()(T a, T b) {
            Print(a * b); } 
    };
    
    template<typename F, typename... Fns>
    void CallFuncs(F a, F b, Fns... fns) {
        (fns(a, b), ...);
    };
    
    void FunctionInvokeTest() { 
        CallFuncs(1, 2, Add{}, Mul{});
    }
    

    T will be deduced from the type of a and b. In this example it will be int. To get double, you need double parameters:

    CallFuncs(1., 2., Add{}, Mul{});
    

    or explicit type specification:

    CallFuncs<double>(1, 2, Add{}, Mul{});
    

    This is very similar to "diamond" functors in the standard library (since C++14). For example, the std::plus declaration is

    template<class T = void>
    struct plus;
    

    If T is void (e.g., in std::plus<>{}), plus::operator() deduces argument and return types. Typical implementation looks like this (with some minor simplifications):

    template<> struct plus<void> {
        template<typename Tp, typename Up>
        constexpr auto operator()(Tp&& t, Up&& u) const {
            return std::forward<Tp>(t) + std::forward<Up>(u);
        }
    };