Home > Net >  Indirect perfect forwarding via function pointer?
Indirect perfect forwarding via function pointer?

Time:05-10

Lets consider ordinary perfect forwarding:

class Test
{
public:
    Test() = default;
    Test(Test const&) { std::cout << "copy\n"; }
    Test(Test&&)      { std::cout << "move\n"; }
};

void test(Test)
{ }

template <typename T>
void f(T&& t)
{
    test(std::forward<T>(t));
}

int main()
{
    std::cout << "expect: copy\n";
    Test t;
    f(t);
    std::cout << "expect: move\n";
    f(Test());
    return 0;
}

So far everything is fine. But if we now introduce a function pointer I get the problem that I seem not to be able to declare universal (forwarding) references:

    decltype(&f<Test>) ptr = &f<Test>;
    // above produces ordinary r-value references
    // making below fail already on compilation:
    ptr(t);

Template function pointers get problematic as well:

template <typename T>
void(*ptr)(T&& t) = &f<T>; // again resolved to r-value reference

At first, they resolve to pure r-value reference as well, additionally they define a bunch of pointers instead of a single one, making the approach unusable within class scope:

class C
{
    template <typename T>
    void(*ptr)(T&& t); // fails (of course...)
};

So question now is: Is it possible at all to have indirect perfect forwarding via function pointers?

Admitted, already fearing the answer is 'no' (and currently falling back to l-value references), but still in hope of having overlooked something somewhere...

CodePudding user response:

The first example uses template argument deduction and reference collapsing rules to compute the correct type. When you explicitly supply the template arguments this disables deduction and the supplied type is substituted directly, resulting in the rvalue reference.

CodePudding user response:

A forwarding reference is not only a hypothetical concept. It is the name given to a specific kind of rvalue reference which has special deduction rules in template argument deduction.

Specifically according to [temp.deduct.call]/3:

A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template (during class template argument deduction ([over.match.class.deduct])). If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.

Except for this special rule (and one other in [temp.deduct.type]), a forwarding reference behaves just like any other rvalue reference.

In particular when providing a template argument for T, there is no template argument deduction happening and substitution is performed directly. In substitution the usual reference collapsing rules are applied.

So f<Test> will yield the function parameter Test&& which is a rvalue reference and f<Test&> will yield the function parameter Test& (collapsed from && being applied to Test&).

These are also the template arguments that would be deduced for a rvalue argument and a lvalue argument in a function call without explicit template arguments. If the reference was not a forwarding reference, then T could never be deduced to a reference and the function parameter would always be a rvalue reference after substitution. The special adjustment of A mentioned in the quote allows T to be deduced to an lvalue reference type, so that the collapsing rules will result in an lvalue reference function parameter as well.

Forwarding references can only exist in templates. A function or specialization of a template cannot have a forwarding reference. They are not somehow a different category of reference from rvalue/lvalue references. They work in templates only by deducing to different types for different value categories of arguments and produce distinct specializations for each value category.


So, since a function pointer must point to a function, not a function template, which of the two specializations for the value category of the argument to choose, has to be decided when taking the function pointer.

If deduction of any kind is expected, then a function pointer cannot offer that. Instead a lambda or functor type should be used which can perform deduction and can choose the function or function template specialization to call based on value category.

CodePudding user response:

Perfect forwarding requires the template argument to be deduced. A function pointer can only point to a single function, not to a set of overloads, and not to a function template.

You can get a function pointer to either the r-value reference instantiation:

decltype(&f<Test&&>) ptr1 = &f<Test&&>;
ptr1(Test());     // output: move

or the l-value instantiation:

decltype(&f<Test&>) ptr2 = &f<Test&>;
ptr2(t);          // output: copy

But not to both at the same time. ptr1 and ptr2 are pointers to functions of different type.

When you want something to hold not just a single function but more than one overload you can use a type with member functions. For example with a lambda expression:

auto fw = [](auto&& t){ test(std::forward<decltype(t)>(t)); };
fw(t);          // output: copy
fw(Test());     // output: move
  • Related