Search code examples
c++templatestype-deduction

Type deduction does not work with std::function


I have the following problem. When I try to compile the following code

template< typename T >
T func( T t)
{
  return t;
}

template< size_t N, typename T >
void foo( std::function< T(T) > func )
{
  // ...
}

int main()
{
  foo<3>( func<float> );

  return 0;
}

I get the error:

 no matching function for call to 'foo'
      foo<3>( func<float> );
      ^~~~~~
/Users/arirasch/WWU/dev/xcode/tests/tests/main.cpp:18:10: note: candidate template ignored: could not match 'function<type-parameter-0-1 (type-parameter-0-1)>' against 'float (*)(float)'
    void foo( std::function< T(T) > func )

However, when I fix it to

template< typename T >
T func( T t)
{
  return t;
}

template< size_t N, typename T >
void foo( std::function< T(T) > func )
{
  // ...
}

int main()
{

  std::function< float(float) > input_func = func<float>;
  foo<3>( input_func );

  return 0;
}

i.e., when I declare the input function of foo explicitly as std::function< float(float) >, the compilation can be done successfully.

Does anyone know how I can fixe my code alternatively so that I can simply write something like foo<3>( func<float> ); (according to my first code example) instead of

std::function< float(float) > input_func = func<float>;
foo<3>( input_func );

where the type of input_func must be explicitly stated?

Many thanks in advance.


Solution

  • Type deduction does not work in your case simply because it cannot be deduced. Type deduction is, in most cases, a simple match with types and other template parameters. There is however some dark corner of C++ that deal with deduction that has some funky rules, but I won't go into it for this answer.

    This is an example where the compiler can deduce template arguments:

    template<typename T>
    void test(std::vector<T>);
    
    test(std::vector<int>{1, 2, 3, 4, 5, 6});
    

    This is easy for the compiler. It need a std::vector of T. You give it a std::vector of int. T must be int.

    However, in your case, There is a lot more stuff happening:

    template<typename T>
    void test(std::function<T(T)>);
    
    int someFunc(int);
    
    test(someFunc);
    

    The compiler can't do the match. Try for yourself: Give me a T that will make those two types equal: int(*)(int) to std::function<T(T)>. Indeed, there is no possible T that can make those two type to be the same, whereas the vector version was an easy match.

    You will say to me: "but... a pointer to a function is convertible to a std::function you silly!" Yeah, it is convertible, indeed. But before any conversion, the compiler has to find what T is. Without T, you make the conversion from a pointer to function to what class? Many class? Try to match every T? There is multiple possibility where your function would be convertible.


    How can you make this work? Forget the std::function. Just receive T.

    template<typename T>
    T func(T t) {
      return t;
    }
    
    template<size_t N, typename T>
    void foo(T func) {
      // ...
    }
    
    int main()
    {
      foo<3>( func<float> );
    
      return 0;
    }
    

    Notice how this example works well. You have no conversion, no std::function thingy and can work with any callable you can possibly imagine!

    Are you worried about accepting any type? No worry here! Parameter types are a bad way to express what a template can do with received parameters anyway. You should restrict it with an expression. That expression will tell others how you will use T and what interface T need to have. btw, we call that sfinae:

    template<size_t N, typename T>
    auto foo(T func) -> decltype(void(func(std::declval<int>()))) {
      // ...
    }
    

    In this example, you restrict func to be callable with a int and still return void.