1

Why does this not work (in C++11) and how can I fix it:

#include <iostream>
#include <functional>

using namespace std;

template <typename T>
int apply(string x, const function<int(T)>& fn)
{
    T t = { x };
    return fn(t);
}

struct S {
    string data;
};

int process_s(const S& s)
{
    return s.data.size();
}

int main()
{
    cout << apply("abc", process_s);
}

The error is

prog.cc:21:8: error: no matching function for call to 'apply'
        cout<<apply("abc", process_s);
              ^~~~~
prog.cc:7:5: note: candidate template ignored: 
  could not match 'function<int (type-parameter-0-0)>'
  against 'int (const S &)'
int apply(string x, const function<int(T)>& fn) {
    ^
1 error generated.

I tried the approach from here, but it doesn't work either:

#include <iostream>
#include <functional>

using namespace std;

template <typename T>
struct id {
    typedef T type;
};

template <typename T>
using nondeduced = typename id<T>::type;

template <typename T>
int apply(string x, const function<nondeduced<int(T)> >& fn)
{
    T t = { x };
    return fn(t);
}

struct S {
    string data;
};

int process_s(const S& s)
{
    return s.data.size();
}

int main()
{
    cout << apply("abc", process_s);
}
max
  • 49,282
  • 56
  • 208
  • 355

2 Answers2

4

The second approach only works when there's another way to deduce T. Your use case has no other function parameter which can be used for that deduction.

The deduction fails in your original attempt because a function pointer/reference (process_s) is not a std::function. Implicit conversion won't count, it has to be an object whose type is a std::function instantiation for the deduction to succeed.

Assuming there's no option to add any more parameters to the function, you are left with two options:

  1. Specify the template argument explicitly, as in apply<S>(...).
  2. Create a std::function object and pass it to your first attempt, as the second function argument.

The second choice can be less ugly in C++17 since there are now deduction guides available. So the invocation can in fact become apply("abc", std::function{process_s});

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Ah thank you! Is there any improvement for this use case in C++17/20? Or is there an inherent problem with type deduction here that cannot be solved without a major type system redesign? – max Mar 25 '18 at 07:21
  • @max - It's not a problem really. The deduction rules as specified keep the system sane and prevent it from being intractable. I'll add a note about C++17, if that's an option. – StoryTeller - Unslander Monica Mar 25 '18 at 07:22
  • @max Your problem is not only about deduction. Suppose you have a functor with 2 similar operators defined. Even with implicit conversion, how can compiler chose which one to use ? – llllllllll Mar 25 '18 at 07:24
  • Is it also a problem that he's passing a string literal to a non const std::string parameter in the first example? At least on my compiler that seems to be what stops it from working after I've fixed up the std::function business. – Zebrafish Mar 25 '18 at 07:50
  • @Zebrafish - It shouldn't be. That parameter is a value. – StoryTeller - Unslander Monica Mar 25 '18 at 07:50
  • @liliscent I didn't quite understand your comment; what does _functor_ and _operator_ refer to? Or maybe you could post a rough code snippet as an example? Thanks! – max Mar 25 '18 at 08:15
2

As @StoryTeller states, an implicit conversion does not count.

The other option that can be considered is, giving up on using std::function. Then it will put you in the right place to start compiling at:

template <typename T>
int apply(string x, int(&fn)(T))
{
    T t = { x };
    return fn(t);
}

Not to mention that you can avoid the overhead of std::function as well.

Dean Seo
  • 5,486
  • 3
  • 30
  • 49
  • But that would prevent me from passing lambdas into the `fn` argument, wouldn't it? – max Mar 25 '18 at 08:10
  • @max, Yes, but `std::function` is not the only option that takes lambdas. Just declare a template parameter `Func` to accept lambdas, which can be more efficient. – Dean Seo Mar 25 '18 at 08:15
  • 1
    Yea, I ended up declaring `template ` instead of the whole business with `std::function`. I agree that the additional type safety of specifying that `Func` is actually a callable with a certain signature isn't worth the required complexity. – max Mar 25 '18 at 08:16