简体   繁体   中英

Passing std::function type which has a templated return type

I have a function

template<typename P = int_fast16_t, typename I>
std::vector<std::vector<P>> f(
    I xb, I xe, I yb, I ye,
    std::function<P(typename std::iterator_traits<I>::value_type,
                    typename std::iterator_traits<I>::value_type)> &&score_function,
    P ID = -1, P S = -1, P M = 1)

It takes two pairs of iterators and a function that should compare two elements of the value_type of the iterators and returns a value of type P.

This gives me an error

./h.hpp:47:32: note: candidate template ignored: could not match 'function<type-parameter-0-0 (typename iterator_traits<type-parameter-0-1>::value_type, typename iterator_traits<type-parameter-0-1>::value_type)>' against 'stringAlgorithms::scoring::plus_minus_one'
   std::vector<std::vector<P>> nw_score_matrix(I xb, I xe, I yb, I ye, 

Now, if I change this to use the specific return type P

template<typename P = int_fast16_t, typename I>
   std::vector<std::vector<P>> nw_score_matrix(I xb, I xe, I yb, I ye, std::function<int_fast16_t(typename std::iterator_traits<I>::value_type, typename std::iterator_traits<I>::value_type)> &&score_function, P ID = -1, P S = -1, P M = 1)

this compiles.

In this case the function plus_minus_one is

  struct plus_minus_one {
     template<typename T, typename R = int_fast16_t>
     R operator()(const T &x, const T &y) { return x == y ? 1 : -1; }
  };

and passed using

scoring::plus_minus_one matchScoring;

and

nw_score_matrix(x.begin(), x.end(), y.begin(), y.end(), matchScoring);

I realize that I could just declare a typename F in the template and make score_function a

F &&score_function

however I want to ensure that if someone creates a functor/lambda that's specific to some types that the function handles the right types. So why doesn't the declaration compile?

EDIT: a full example is found https://github.com/meconlen/stringAlgorithms/tree/soq

To call the function, you'll need to actually give it a std::function :

scoring::plus_minus_one matchScoring;
std::function<int_fast16_t(int,int)> score_function = matchScoring;
nw_score_matrix(x.begin(), x.end(), y.begin(), y.end(), std::move(score_function));

Otherwise, there are too many conversions for the compiler to figure out what you are trying to do.

A brief of explanation. When you write:

template <typename P = int>
void f(std::function<P()>) {}

then P is still in a deduced context, meaning the compiler will try to deduce P based on the type of an argument expression, ignoring the default value ( int ). However, type deduction only deduces types, ie, the type of the argument expression must match the type of the corresponding parameter, so that the compiler will deduce the missing type template parameters.

So, once you call the function like below:

std::function<float()> a;
f(a);

the compiler will substitute P with float .

Now, if you wanted to pass in eg an address of a function:

char foo() { return {}; }
f(&foo);

the compiler will complain that it can't deduce P , because:

  1. It is still in a deduced context.
  2. The type of the argument ( char(*)() ) does not match the type of the parameter std::function<P()> .

That is, the compiler doesn't know that P from the signature of a std::function<P()> should be matched against the return type of a function pointer, char .

You could, say, force the type explicitly:

f<char>(&foo);

but that isn't very flexible.

You could also put std::function in a non-deduced context, and let P be deduced elsewhere:

template <typename T> struct identity { using type = T; };

template <typename P>
void f(typename identity<std::function<P()>>::type, P c) {}

f(&foo, 'a'); // P will be deduced from the 'a' argument

But this won't work if c is defaulted and not specified in a function call arguments list.

Nothing stops you from deducing the exact type of the argument expression:

template <typename F>
void f(F&& f) {}

f(&foo);

If you care, you can always check if whatever f returns is convertible to P , or just read that type.

In your scenario, an instance of a completely unrelated type ( plus_minus_one ) is passed as an instance of std::function , so clearly, deduction fails. The compiler doesn't know that it has to look at its operator() , and use its defaulted return type.

std::function itself is a type-eraser, in your case you don't really need to erase any type.

Using the suggestion provided by Piotr Skotnicki, I obtained the following possible solution for you. You want C++ to deduce the type of score_function itself, rather than the nested type of it. However, once you have that type - let's call it F , then you can manually retrieve what you need.

This helper trait struct extracts P from your code for given functional type F (that's basically Piotr Skitnicki's comment)

template<typename F, typename I>
struct PP {
    typedef typename std::decay<typename std::result_of<
      F(typename std::iterator_traits<I>::value_type,
        typename std::iterator_traits<I>::value_type)
      >::type>::type type;
};

Now, with this help you can define your function as:

template<typename I, typename F>
   std::vector<std::vector<typename PP<F,I>::type>>
     nw_score_matrix(I xb, I xe, I yb, I ye,
                     F &&score_function,
                     typename PP<F,I>::type ID = -1,
                     typename PP<F,I>::type S = -1,
                     typename PP<F,I>::type M = 1)
   {
       typedef typename PP<F,I>::type P;

       ....... your existing code ......
   }

... and then you can call the function without any changes at the call site. In particular, you don't need to convert matchScoring to std::function object.

I believe it matches your requirement that F might be a functor, std::function or a lambda - it should work in all cases.

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