I am curious as to why a2
in is fine, but b2
won't compile:
#include <functional>
#include <iostream>
class A {
public:
A(std::function<void(int)>&& func) : f(std::move(func)) {}
std::function<void(int)> f;
};
template <class F>
class B {
public:
B(F&& func) : f(std::move(func)) {}
F f;
};
int main() {
int a = 1;
auto f = [a](int b){std::cout << a b << '\n';};
A a1([a](int b){std::cout << a b << '\n';});
A a2(f);
B b1([a](int b){std::cout << a b << '\n';});
// B b2(f);
a1.f(2);
a2.f(2);
b1.f(2);
// b2.f(2);
f(2);
}
CodePudding user response:
A(std::function<void(int)>&& func)
A
can be initialized with std::function
rvalue. Now, f
is not a std::function
, seeing as each lambda has its own distinct type. But we can create a temporary std::function
out of it, and bind the rvalue reference func
to that.
B(F&& func)
Don't let appearances fool you. This may look like a forwarding reference, but it isn't. Forwarding references are syntactically rvalue reference to a template parameter, but it must be a template parameter of the function we are forwarding into.
The constructor of B
is not a template, and so func
is not a forwarding reference.
The deduction guide that gets generated from that constructor accepts only rvalues, and deduces F
from that. Because f
(the local lambda in main
) is an lvalue, it cannot bind to an rvalue reference and so CTAD cannot succeed. Indeed std::move(f)
will make b2
well-formed.
If you want to accept lvalues for arguments as well, you may add another constructor
B(F const& func) : f(func) {}
Now there are two deduction guides being generated, one for each value category.
CodePudding user response:
B b2(f);
B
is not a class, or a type, it is a template. An object declaration in C is, basically a declaration: here's a type, and here's one or more objects of that type (or a derived type). A template is not a type. A template instantiation is a type.
std::vector b2;
This won't compile for the same reason your code will not compile. You have to specify the template parameter to instantiate a type, such as:
std::vector<int> b2;
The same reason explains your compilation error.
Having said that, a small change to your template will make it compile as long as your compiler supports C 17:
template <class F>
class B {
public:
B(F func) : f(std::move(func)) {}
F f;
};
A C 17 compiler will be able to deduce the template parameter due to class template deduction guides in C 17. And, it's possible that with a little bit of additional work (I haven't looked into it) it might be possible to fiddle a few things and make your original template also work in C 17.