1

I have a function declared as follows; its exact working is not relevant for this.

template<typename T>
std::pair<int, int>
partition3(T *pT, const int N, const T &Kq, const int w,
           std::function<int(const T&, const T&, int)> P);

At the call site I attempt to do the following:

bool partition3_test()
{
  struct cmp
  {
    int operator()(int x, int y, int) const
    { return x-y; }
  };

  int V1[11] = { 3, 7, 1, 7, 7, 8, 10, 2, 16, 4, 3 },
      V2[11] = { 3, 6, 1, 6, 6, 8, 10, 2, 16, 4, 3 };

  std::function<int(const int&, const int&, int)> F = cmp();

  std::pair<int, int>
    p1  = partition3(V1, 11, 7, 0, cmp()),
    p2  = partition3(V2, 11, 7, 0, cmp());

  return false;
}

For the two calls of partition3 the compiler (MSVC 2010) complains that it could not deduce template argument for the last parameter. If I replace cmp() with F, the code compiles and works fine.

I have two questions:

  1. Why do I get the error? [compiler bug or some arcane C++ rule?]
  2. How can I achieve the same effect without first explicitly constructing F?

(Currently, I have solved the problem by introducing another template parameter on partition3 and declaring P as that template type.)

zvrba
  • 24,186
  • 3
  • 55
  • 65

1 Answers1

4

cmp() isn't actually a std::function at all. The fact that the copy-initialization works may be confusing the issue, but that uses a converting constructor which must be using some sort of wrapper object, and I'm surprised that it works against a temporary functor object (ah, checking the standard it apparently make a copy of the functor).

On top of that, the function arguments don't match (pass-by-value and pass-by-const-reference are source-compatible, but not runtime-call-compatible), which again requires an adaptor.

The best solution is to make the template function more generic, so that it also works with raw function pointers and arbitrary functor objects, not just std::function:

template<typename T, typename Functor>
std::pair<int, int> partition3(T *pT, const int N, const T &Kq, const int w,
                               const Functor& P);

If for some reason you really wanted to use std::function (you need the virtual dispatch, for instance), you can. The error, quite contrary to what Nawaz wrote, is that this is a deducible context, but more than one type fits (this is where the adaptor/wrapper nature of std::function becomes important, because the type doesn't have to exactly match the parameter, it just has to be compatible. std::function<int(const long&, const long&, int)> would match just as well.)

Use instead a non-deducible context, that way the compiler won't even try to use the functor for deduction of T.

template<typename T, typename Functor>
std::pair<int, int> partition3(T *pT, const int N, const T &Kq, const int w,
                               std::function<std::identity<const T>::type&, std::identity<const T>::type&, int> P);
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • @Luc: The adaptor is inside `std::function`. – Ben Voigt Apr 15 '12 at 15:15
  • 2
    @Luc: It also adds a virtual function call. I think it's important to know that accepting a `std::function` introduces overhead compared to the solution I recommend. – Ben Voigt Apr 15 '12 at 15:55
  • @LucDanton it's useful information and he also answered a what would have been a follow-up question: whether using `std::function` introduces a run-time overhead. – zvrba Apr 15 '12 at 18:18
  • @BenVoigt You pay the costs due to type-erasure, not due to adapting the arguments (i.e. even if the types matched). You should clarify that. – Luc Danton Apr 15 '12 at 18:55
  • @Luc: If there were no type erasure, a good optimizer could probably eliminate the cost of adapting the arguments when inlining. But they aren't exactly costs added by the virtual call, because you could have a virtual call without such costs. (I'm thinking of x64, where the `int` arguments would be passed in registers. A wrapper that accepts `const int&` arguments, which are of necessity addresses, does more work than a wrapper which accepts `int` arguments, when both introduce a virtual call which prevents inlining.) – Ben Voigt Apr 15 '12 at 19:06
  • I've rescinded my upvote. I think you've made the whole affair very confusing. I don't think my comments lead the discussion in the direction I wanted so I removed them, too. – Luc Danton Apr 15 '12 at 20:24