2

Here's a small program in its entirety. The first three calls to test1() and test2() compile and run properly, the last call to test2() doesn't compile. How can I get the compiler to recognize the call to test2() without specifying the type in the call?

#include <functional>
#include <iostream>

// using function pointer
template <typename T>
T test1(T arg, T (*fnptr)(T)) {
  return fnptr(arg);
}

// using a lambda
template <typename T>
T test2(T arg, std::function<T (T)> mapfn) {
  return mapfn(arg);
}

int dbl(int v) {
  return 2 * v;
}

int main() {
  // v1a: compiles, runs without error
  int v1a = test1<int>(11, dbl);
  std::cout << v1a << std::endl;

  // v2a: compiles, runs without error
  int v2a = test2<int>(11, [=](int arg) { return 2 * arg; });
  std::cout << v2a << std::endl;

  // v1b (without template type): compiles, runs without error
  int v1b = test1(11, dbl);
  std::cout << v1b << std::endl;

  // v2a (without template type): doesn't compile: no matching fn
  int v2b = test2(11, [=](int arg)->int { return 2 * arg; });
  std::cout << v2b << std::endl;

  return 0;
}

The compiler generates the following message:

$ g++ -O3 -Wall -std=c++11 -o sketch_tiny sketch_tiny.cpp 
sketch_tiny.cpp:34:13: error: no matching function for call to 'test2'
  int v2b = test2(11, [=](int arg)->int { return 2 * arg; });
            ^~~~~
sketch_tiny.cpp:12:3: note: candidate template ignored: could not match
      'function<type-parameter-0-0 (type-parameter-0-0)>' against '<lambda at
      sketch_tiny.cpp:34:23>'
T test2(T arg, std::function<T (T)> mapfn) {
  ^
1 error generated.

Is there a way to get the compiler to recognize the call to test2(...) without using the test2<int>(...) type specifier?

For what it's worth, here's the compilation environment:

$ g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 6.0 (clang-600.0.57) (based on LLVM 3.5svn)
Target: x86_64-apple-darwin14.1.0
Thread model: posix
fearless_fool
  • 33,645
  • 23
  • 135
  • 217
  • The difference is that a lambda is not a `std::function`, but `dbl` decays to a function pointer. – T.C. Mar 23 '15 at 02:27
  • @T.C. Why can't the deduction happen from the first argument only? – Kam Mar 23 '15 at 03:58
  • T.C.: I agree that the original question was similar to http://stackoverflow.com/questions/9998402/c11-does-not-deduce-type-when-stdfunction-or-lambda-functions-are-involved. I've since re-worded the question to ask the real question: _How do I force the compiler to match a lambda in a templated function?_ Although I now know the answer (use a template with two types, i.e. `template `), this question should be re-opened since the answer would be generally useful (and you really have to dig to find the answer in the other question). – fearless_fool Mar 23 '15 at 07:52

1 Answers1

4

Template argument deduction looks through only a very limited set of implicit conversions. A lambda is not a std::function, so template argument deduction fails.

There are usually two ways to solve this:

  1. Take the lambda's type as a separate template parameter:

    template <typename T, typename F>
    T test2(T arg, F mapfn) {
      return mapfn(arg);
    }
    

    This is the most efficient way.

  2. If for some reason you really want to use std::function and pay the associated type erasure costs, you can put the std::function<T(T)> into a non-deduced context.

    template <typename T> struct identity { using type = T; };
    template <typename T> using non_deduced = typename identity<T>::type;
    
    template <typename T>
    T test2(T arg, non_deduced<std::function<T (T)>> mapfn) {
      return mapfn(arg);
    }
    
T.C.
  • 133,968
  • 17
  • 288
  • 421
  • Using the lambda's type as a template parameter (first solution) works like a champ for me. If you do it that way, you can pass either a lambda or a function pointer to test2() -- a nice bonus. – fearless_fool Mar 23 '15 at 16:13