2

I've got the following code, which can't be compiled

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

int main() {
    auto print = [](int x) { std::cout << x; };
    call_with(print, 42);
}

And the compilation error looks like

tst.cpp:17:2: error: no matching function for call to 'call_with'
        call_with(print, 42);
        ^~~~~~~~~
tst.cpp:11:6: note: candidate template ignored: could not match 'function<void (type-parameter-0-0)>' against
      '(lambda at tst.cpp:16:15)'
void call_with(std::function<void(T)> f, T val) {
     ^
1 error generated

I tried to compile it with g++ tst.cpp -o tst -std=c++17

So I know sometimes C++ can deduce template arguments under some conditions, but I'm wondering why it cannot compile this code in this case. It looks like there cannot be any other options for a type T to be anything, but int and for f to be anything but std::function<void(int)>.

Michał Słodki
  • 168
  • 1
  • 2
  • 8
  • 1
    `std::function` can be constructed from `[](int){}`. So just because the lambda takes a parameter of type `T` doesn't mean the `std::function` has to take the parameter of the same type. – Igor Tandetnik Nov 29 '20 at 13:51
  • Ok, makes sense, but if we leave only ```T val``` argument, it is deduced correctly as ```int``` even though ```42``` can be treated as ```double``` So why there's an ambiguity with ```std::function``` and no ambiguity with a basic template argument ```T```? – Michał Słodki Nov 29 '20 at 13:57
  • Upon further consideration, I believe my first comment is misleading (it's technically correct but irrelevant to the question). The real reason is that template parameter deduction doesn't consider implicit conversions - it only performs a simple pattern matching on types. And the type `class L` (some unnamed class type synthesized for the lambda expression) doesn't match `std::function` – Igor Tandetnik Nov 29 '20 at 14:01
  • 2
    `42` cannot be treated as a double. `42` as an expression is an int, which can match a `T`. It's concrete. `std::function` erases the type of the callable (any callable) and it does so by conversions. Templates will not consider conversions when deducing things, by design, ever. – StoryTeller - Unslander Monica Nov 29 '20 at 14:02
  • Consider: `std::function` has a constructor `template< class F > function( F f );` - on the surface, this can take anything. There's no way for the compiler to know that it's supposed to search the argument for all ways that a function call operator could be applied to it, and then pattern-match the types of parameters of such a function call - that's part of the semantics of `std::function` that's not in any way reflected in the syntax. – Igor Tandetnik Nov 29 '20 at 14:04
  • 1
    As a workaround, you could place the first `T` into a non-deduced context, e.g. as `template void call_with(std::function)> f, T val)` (`type_identity_t` is a C++20 feature, but an equivalent can be trivially hand-rolled for earlier versions). This way, `T` would be unambiguously deduced from the second argument. – Igor Tandetnik Nov 29 '20 at 14:08
  • Hm, ok, interesting. So the compiler can actually deduce everything like there's enough information for it, but due to semantics of ```std::function``` it doesn't do that. – Michał Słodki Nov 29 '20 at 14:16
  • 1
    `call_with(print, 42);` should fix the problem. – macroland Nov 29 '20 at 14:17
  • Expounding on *Templates will not consider conversions when deducing things, by design, ever.* Why? Because compile times would take *factorially* longer. And that's bad. – Eljay Nov 29 '20 at 14:19
  • @Eljay Yeeeah, that looks like a true reason. Hm, but why is it factorial? – Michał Słodki Nov 29 '20 at 14:26
  • 1
    If you could restrict all functions to be monads, then it would only be *n*, but if the conversions are for every parameter then you have *n* x *n* x ... x *n*. – Eljay Nov 29 '20 at 14:58

1 Answers1

2

Because print is not a std::function<void(int)>.And it can be converted to more than one valid type.

Try Run

    auto print = [](int x) { std::cout << x; };
    std::cout << typeid(print).name()<<"\n";
    std::function<void(unsigned __int64)> print_a = std::function<void(unsigned __int64)>(print);
    std::cout << typeid(print_a).name() << "\n";
    auto print_b = std::function<void(unsigned __int64)>(print);
    std::cout << typeid(print_b).name() << "\n";

You will get

class <lambda_d103cbf184cf5bdcf1494399ee6d564a>
class std::function<void __cdecl(unsigned __int64)>
class std::function<void __cdecl(unsigned __int64)>

Which shows print is a lambda,and it can be converted to many kinds of std::function<void(T)> like:

    auto print = [](int x) { std::cout << x; };
    auto print2 = std::function<void(unsigned __int64)>(print);
    auto print3 = std::function<void(unsigned short)>(print);
    auto print4 = std::function<void(char)>(print);

An exact std::function<void(int)> object can be deduced:

std::function<void(int)> print = [](int x) { std::cout << x; };
call_with(print, 42);

And 42 also need to be right type for deducing

std::function<void(int)> + int means T=int

std::function<void(short)> + short means T=short

but std::function<void(short)> + int can't be deduced. So you will get compile error on :

std::function<void(short)> print = [](short x) { std::cout << x; };
call_with(print, 42);

The right version is

std::function<void(short)> print = [](short x) { std::cout << x; };
call_with(print, short(42));
Kahn
  • 206
  • 1
  • 7
  • 1
    Mm, it seems a bit strange to me, like there's the same template typename ```T```, so if there are different candidates for ```function(T)```, why can't compiler just take all the candidates, try then to deduce the second argument of the function (which is definitely ```int```) and leave only one candidate among all those, which is ```function(int)``` – Michał Słodki Nov 29 '20 at 14:22