4

Why is it legal to pass generic lambdas to std::thread() without any specialization, while, on the other hand, this is illegal with function templates.

The following demonstrates my query.

#include <thread>

template <class T>
void f(T t)
{
}

int main()
{
    std::thread t([](auto i){}, 1);  // Works
    std::thread t(f, 1);          // Doesn't work
    std::thread t(f<int>, 1);     // Works

    t.join();
    return 0;
}
alex35833
  • 107
  • 6
  • 1
    Since deduction type can't be done based on internal implementation. It happens based on function (here constructor) arguments. Now lambda is instance of specific type with function which is a template. Take a look on this: https://cppinsights.io/s/e0e75c1c – Marek R Jul 25 '23 at 10:11
  • @MarekR could you please elaborate... – alex35833 Jul 25 '23 at 10:14
  • 5
    A template function is just a template, a blueprint for a function, but it's not an actual function. You need to say which explicit function you want to use. A lambda, even a generic one like you have, is not a template. It's an actual object with a specific function-call operator overload. – Some programmer dude Jul 25 '23 at 10:16
  • 2
    Again generic lambda is a solid type (see https://cppinsights.io/s/e0e75c1c). So not a template is passed to thread, but object with well defined type. Then when lambda is invoked there is attempt to instantiate member function template which is `operator()(auto)`. – Marek R Jul 25 '23 at 10:20
  • 1
    A lambda is effectively an instance of a compiler-defined type with an `operator()` on it. So, `std::thread t([](auto i){}, 1);` is basically like: `struct { void operator()(auto i){} }; instance; std::thread t(instance, 1);` – Remy Lebeau Jul 25 '23 at 20:58

1 Answers1

9

C++ consists of many different kinds of things. Some of these things are: objecs, types, and templates. They are completely different things, that serve their own distinct, unique purposes. There's a right place in a C++ program to use an object, a right place to use a type, and a right place to use a template. When you have a right place for an object, you can't use a type or a template. When you have a right place to use a type you can't use an object or a template. When you have a right place to use a template you can't use an object or a type. This is fundamental to C++.

The parameters to a function call, or a constructor, are always objects. They are never types, or templates.

    std::thread t([](auto i){}, 1);  // Works

Both parameters are objects. This is correct. A lambda is an object, an instance of a unique, anonymous type. 1 is also an object: an integer whose value is 1.

    std::thread t(f, 1);          // Doesn't work

f is not an object. It is a template.

    std::thread t(f<int>, 1);     // Works

This f<int> is an object, a pointer to a function that was instantiated from a template.

An identifier that corresponds to a name of a template, by itself, results in a template. It does not result in a type, or an object. When you specify template parameters you end up with a type, and in a special case of a type that refers to a template function you get a pointer to the template function instance, an object (just like using the name of an ordinary non-template function, by itself, gives you a pointer to that function).

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148