Home > OS >  difference between using a template function type and std::function
difference between using a template function type and std::function

Time:11-15

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.

  • Related