3

While exploring templates in C++, I stumbled upon the example in the following code:

#include <iostream>
#include <functional>

template <typename T>
void call(std::function<void(T)> f, T v)
{
    f(v);
}

int main(int argc, char const *argv[])
{
    auto foo = [](int i) {
        std::cout << i << std::endl;
    };
    call(foo, 1);
    return 0;
}

To compile this program, I am using the GNU C++ Compiler g++:

$ g++ --version // g++ (Ubuntu 6.5.0-1ubuntu1~16.04) 6.5.0 20181026

After compiling for C++11, I get the following error:

$ g++ -std=c++11 template_example_1.cpp -Wall

template_example_1.cpp: In function ‘int main(int, const char**)’:
template_example_1.cpp:15:16: error: no matching function for call to ‘call(main(int, const char**)::<lambda(int)>&, int)’
     call(foo, 1);
                ^
template_example_1.cpp:5:6: note: candidate: template<class T> void call(std::function<void(T)>, T)
 void call(std::function<void(T)> f, T v)
      ^~~~
template_example_1.cpp:5:6: note:   template argument deduction/substitution failed:
template_example_1.cpp:15:16: note:   ‘main(int, const char**)::<lambda(int)>’ is not derived from ‘std::function<void(T)>’
     call(foo, 1);
                ^

(same for C++14 and C++17)

From the compiler error and notes I understand that the compiler failed to deduce the type of the lambda, since it cannot be matched against std::function.

Looking at previous questions (1, 2, 3, and 4) regarding this error, I am still confused about it.

As pointed out in answers from questions 3 and 4, this error can be fixed by explicitly specifying the template argument, like so:

int main(int argc, char const *argv[])
{
    ...
    call<int>(foo, 1); // <-- specify template argument type
    // call<double>(foo, 1) // <-- works! Why?
    return 0;
}

However, when I use other types instead of int, like double, float, char, or bool, it works as well, which got me more confused.

So, my questions are as follow:

  • Why does it work when I explicitly specify int (and others) as the template argument?
  • Is there a more general way to solve this?
omar
  • 629
  • 4
  • 13

1 Answers1

12

A std::function is not a lambda, and a lambda is not a std::function.

A lambda is an anonymous type with an operator() and some other minor utility. Your:

auto foo = [](int i) {
    std::cout << i << std::endl;
};

is shorthand for

struct __anonymous__type__you__cannot__name__ {
  void operator()(int i) { 
    std::cout << i << std::endl;
  }
};
__anonymous__type__you__cannot__name__ foo;

very roughly (there are actual convert-to-function pointer and some other noise I won't cover).

But, note that it does not inherit from std::function<void(int)>.


A lambda won't deduce the template parameters of a std::function because they are unrelated types. Template type deduction is exact pattern matching against types of arguments passed and their base classes. It does not attempt to use conversion of any kind.


A std::function<R(Args...)> is a type that can store anything copyable that can be invoked with values compatible with Args... and returns something compatible with R.

So std::function<void(char)> can store anything that can be invoked with a char. As int functions can be invoked with a char, that works.

Try it:

void some_func( int x ) {
  std::cout << x << "\n";
}
int main() {
  some_func('a');
  some_func(3.14);
}

std::function does that some conversion from its signature to the callable stored within it.


The simplest solution is:

template <class F, class T>
void call(F f, T v) {
  f(v);
}

now, in extremely rare cases, you actually need the signature. You can do this in :

template<class T>
void call(std::function<void(T)> f, T v) {
  f(v);
}
template<class F, class T>
void call(F f_in, T v) {
  std::function f = std::forward<F>(f_in);
  call(std::move(f), std::forward<T>(v));
}

Finally, your call is a crippled version of std::invoke from . Consider using it; if not, use backported versions.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • yea that ought to do it. was just about to write the same thing. – Kagiso Marvin Molekwa Nov 15 '18 at 19:09
  • Also, the explicit cast works because it's not template any more and so the implicit conversion can kick in. – Quimby Nov 15 '18 at 19:13
  • You should take in a `F&&` and `T&&` if you're going to use `std::forward`. – tkausl Nov 15 '18 at 19:15
  • @tkausl If someone passes template arguments explicitly, `std::move` would be wrong. `std::forward` is correct regardless. I considered using `std::move`, but nothing but convention guarantees that `F` and `T` are not references, so I went with correctness over simplicity. – Yakk - Adam Nevraumont Nov 15 '18 at 19:19
  • @Yakk-AdamNevraumont thanks for your thorough answer. I am interested in passing a lambda to my `cal()`l function, and since lambda is a `callable`, I thought that the best way to pass it is to wrap it in a `std::function`([Instances of std::function can store, copy, and invoke any Callable target](https://en.cppreference.com/w/cpp/utility/functional/function)) – omar Nov 15 '18 at 19:53
  • @omar `std::function` is a type-erasure class. Type erasure and template type deduction are pseudo-inverses of each other. Doing both is code smell, and usually a sign of a design error. It would be like having a function that takes a `std::mutex` and unlocks it and relocks it. Or a function that doubles an `int`, halves it, then returns that value. Doing both is *almost* the same as doing nothing, except in some cases when it usually is a bug. *Extremely rarely* doing that can be on purpose and a good plan. – Yakk - Adam Nevraumont Nov 15 '18 at 19:55