简体   繁体   中英

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;
}

ie, 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.

The problem here is that the compiler must do overload resolution to find out which std::function<U(U)> instantiations have constructor(s) that can take a T(*)(T) . That is to say, there might be multiple types each of which may have multiple ctors that could take your input_func .

Now, if you look at the Standard, you'll find that there is no such overload specified for std::function , but the overload resolution rules are the same for all templates, whether they're from std:: , boost:: or ACME:: . The compiler won't start instantiating templates to find a conversion sequence.

Once you provide a perfect match, there's no need for a conversion sequence. There's exactly one target type for which no conversion sequence is needed, and the compiler deduces this type.

In this particular case, you know about the relation between function pointers and std::function , and also about the specific restriction that you must have a unitary function which returns the same type (no T(*)(U) ) so you could add an overload

template< size_t N, typename T >
void foo(T(*func)(T))
{
  return foo<N>(std::function<T(T)>(func));
}

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 .

Casting func to an std::function explicitly solves the problem. Eg the code below works.

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

template<typename T>
using FuncType = std::function<T(T)>;

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

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

  return 0;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM