Search code examples
c++c++11variadic-templates

How to do variadic deduction in std::function's parameters?


I try to implement a function f: (std::function -> int) which will pass 1s into input_functor with c++ variadic template.

Let input_functor be g.

For example:

  • If g is std::function<int(int,int)>, then f return g(1, 1).
  • If g is std::function<int(int,int,int)>, then f return g(1, 1, 1).
  • If g is std::function<int(int,int,int,int)>, then f return g(1, 1, 1, 1).
#include <functional>
#include <iostream>

template <typename T, typename... Args>
int apply(std::function<int(T, Args...)> func) {
    auto tmp = [func](Args... args) {
        return func(1, args...);
    };
    return apply(tmp);
}

template <typename T>
int apply(std::function<int(T)> func) {
    return func(1);
}

int main() {
    std::function<int(int, int)> f = [](int a, int b) {
        return a + b;
    };
    std::cout << apply(f) << "\n";
    return 0;
}

The compiler (clang++) error msg is that it cannot match candidates.

main.cpp:9:12: error: no matching function for call to 'apply'
    return apply(tmp);
           ^~~~~
main.cpp:21:18: note: in instantiation of function template specialization 'apply<int, int>' requested here
    std::cout << apply(f) << "\n";
                 ^
main.cpp:5:5: note: candidate template ignored: could not match 'function<int (type-parameter-0-0, type-parameter-0-1...)>' against
      '(lambda at main.cpp:6:16)'
int apply(std::function<int(T, Args...)> func) {
    ^
main.cpp:13:5: note: candidate template ignored: could not match 'function<int (type-parameter-0-0)>' against '(lambda at main.cpp:6:16)'
int apply(std::function<int(T)> func) {
    ^
1 error generated.

Solution

  • You have 2 issues:

    • definition order:

      template <typename T>
      int apply(std::function<int(T)> func) {
          return func(1);
      }
      

      should be place before the recursive function to allow to be visible and ends recursion.

    • lambda is not a std::function, so deduction don't happen

      template <typename T, typename... Args>
      int apply(std::function<int(T, Args...)> func) {
          auto tmp = std::function{[func](Args... args) { // C++17 CTAD
              return func(1, args...);
          }};
          return apply(tmp);
      }
      

    Demo C++17

    As you are limited to C++11, you might create traits to know which std::function is needed:

    template <typenate T, typename Discarded>
    struct always_first
    {
        using type = T;
    };
    template <typenate T, typename Discarded> using always_first_t = typename always_first<T, Discarded>::type;
    
    // possibly directly
    // template <typenate T, typename Discarded> using always_first_t = T;
    // but old compilers might have some issues with that construct as std::void_t
    

    and then

    std::function<int(always_first_t<int, Args>...)> tmp = /* your lambda */;