6

The basis from my question I took from here: Failure to deduce template argument std::function from lambda function The question in this thread is: Why this code can't pass the lambda to the function:

#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;
}

The answer in this thread is, since a lambda isn't a std::function. But why is this code compiling:

#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); // changed foo to {foo}
    return 0;
}
lubgr
  • 37,368
  • 3
  • 66
  • 117
JulianW
  • 897
  • 9
  • 23

1 Answers1

5

As written in the linked answer, the first version does not compile, because template argument deduction fails for the first argument; a lambda is never an std::function<void(T)>.

The second version compiles, because, by writing {foo}, std::function<void(T)> becomes a non-deduced context. So deduction can no longer fail for the first argument, as it isn't even attempted. So T is deduced as int solely from the second argument, and the call succeeds, because the lambda is convertible to an std::function<void(int)>.

From [temp.deduct.type]:

The non-deduced contexts are:

  • ...
  • A function parameter for which the associated argument is an initializer list but the parameter does not have a type for which deduction from an initializer list is specified.
Mark
  • 1,016
  • 6
  • 10
  • So a call to `template call(std::function f)` with a lambda would fail right? – JulianW Aug 29 '19 at 09:06
  • 1
    And it would fail if `std::function` would have a constructor with `std::initializer_list` as argument? – JulianW Aug 29 '19 at 09:14
  • 1
    No, that doesn't matter here. Deduction from an initializer list argument only happens if the parameter is itself an initializer list or an array. For instance: `template max(std::initializer_list values)` can deduce `T = int` from `max({1, 2, 3})`. – Mark Aug 29 '19 at 09:20
  • Nice man, so `template void call(decltype(std::function{}) f, T v)` with `call([](int a) {}, 1)` should work as well. I currently feel like I rediscovered the wheel. So many years C++ and then this. Thanks :) – JulianW Aug 29 '19 at 09:37
  • 1
    Yes. Additionally, the future-standard-approved way of introducing a non-deduced context is using [std::type_identity](https://en.cppreference.com/w/cpp/types/type_identity) – Mark Aug 29 '19 at 09:39