Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template function as template argument

I'm learning about templates and tried to implement this method:

template <typename Func, typename Left, typename Right>
void flipArgs(Func* function, Left&& leftArg, Right&& rightArg) {
    function(std::forward<Right>(rightArg), std::forward<Left>(leftArg));
}

It takes a function and two parameters and calls the given function with the two parameters flipped.

It works fine with function such as:

void test1(std::string, int) {
}

When I tried this function:

template <typename T>
void test2(T&& a, int) {
}

With:

string s("test");
flip(test2<string>, 42, s);

The compiler (g++ 4.7.1) tells me:

error: cannot bind 'std::basic_string' lvalue to 'std::basic_string&&'

I thought that a function parameter such as T&& was a special case that can bind to rvalue and lvalue references? What am I doing wrong?

like image 875
Timo Türschmann Avatar asked Mar 21 '23 23:03

Timo Türschmann


1 Answers

I thought that a function parameter such as T&& was a special case that can bind to [rvalues and lvalues]?

It is. It basically means the template can have different instantiations for lvalues and for rvalues.

However... When you explicitly make T be string in test2<string>, you are picking one particular instantiation: void test2(string&&, int). string&& is no longer that special case. string&& can only bind to string rvalues. There isn't one instantiation that binds to both rvalues and lvalues.

In general, I'd recommend against explicitly passing function template parameters (unless those are intended, like std::forward or std::make_unique).

In this case, you could instead force one of the instantiations that binds to lvalues. Something like flip(test2<string&>, 42, s);, which will instantiate void test2(string&, int).

If you really want to pass an argument to flip that can accept both lvalues and rvalues, you need a polymorphic function object:

struct test2 {
    template <typename T>
    void operator()(T&& a, int) const {
    }
};
flip(test2{}, 42, s);

The key here is that the decision of which specialisation to use is not made when passing the argument, but only later on when that argument is used.

For completeness, in C++14 you can actually create anonymous polymorphic function objects with the new lambda syntax:

auto test2 = [](auto&& a, int) {};
flip(test2, 42, s);
like image 103
R. Martinho Fernandes Avatar answered Apr 02 '23 04:04

R. Martinho Fernandes