通过函数指针间接完美转发?

Indirect perfect forwarding via function pointer?

让我们考虑普通完美转发:

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;
}

到目前为止一切都很好。但是如果我们现在引入一个函数指针,我会遇到一个问题,我似乎无法声明 universal (forwarding) references:

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

模板函数指针也有问题:

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

起初,他们也解析为纯 r 值引用,另外他们定义了 一堆 指针而不是单个指针,使得该方法在 class范围:

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

所以现在的问题是:是否有可能通过函数指针进行间接完美转发?

承认,已经担心答案是 'no'(目前正在回落到左值引用),但仍然希望在某处忽略了一些东西.. .

第一个示例使用模板参数推导和 reference collapsing rules 来计算正确的类型。当您显式提供模板参数时,这会禁用推导并直接替换提供的类型,从而产生右值引用。

转发参考不仅仅是一个假设的概念。它是一种特定类型的右值引用的名称,在模板参数推导中有特殊的推导规则。

具体根据[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.

除了这个特殊规则(以及 [temp.deduct.type] 中的另一个规则),转发引用的行为与任何其他右值引用一样。

特别是在为 T 提供模板参数时,不会发生模板参数推导,而是直接执行替换。作为替代,应用了通常的引用折叠规则。

因此 f<Test> 将生成函数参数 Test&&,它是一个右值引用,而 f<Test&> 将生成函数参数 Test&(从 && 折叠正在应用于 Test&)。

这些也是在没有显式模板参数的函数调用中为右值参数和左值参数推导的模板参数。如果引用不是转发引用,则 T 永远不会被推导为引用,并且函数参数在替换后将始终是右值引用。引用中提到的 A 的特殊调整允许 T 被推导为左值引用类型,因此折叠规则也会导致左值引用函数参数。

转发引用只能存在于模板中。模板的函数或特化不能有转发引用。它们与 rvalue/lvalue 参考文献在某种程度上并没有不同的参考类别。它们仅通过为参数的不同值类别推导出不同类型并为每个值类别产生不同的专业化来在模板中工作。


因此,由于函数指针必须指向 函数,而不是函数模板,所以要选择的参数值类别的两个特化中的哪一个必须是取函数指针时决定。

如果需要任何类型的推导,那么函数指针无法提供。相反,应该使用 lambda 或仿函数类型,它们可以执行推导,并可以根据值类别选择要调用的函数或函数模板特化。

完美转发需要推导模板参数。函数指针只能指向单个函数,不能指向一组重载,也不能指向函数模板。

您可以获得指向 r-value 引用实例化的函数指针:

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

或l-value实例化:

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

但不能同时进行。 ptr1ptr2 是指向不同类型函数的指针。

当您希望某些东西不仅包含一个函数而且包含多个重载时,您可以使用带有成员函数的类型。例如,使用 lambda 表达式:

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

问题的背景是 Scott Meyer 的 article about forwarding references 的 mis-reading(那里称为 'universal references')。

这篇文章给人的印象是,这种转发引用作为一种单独的类型 与普通的 l-value 和 r-value 引用并行。但是,情况并非如此,相反,它是一种假设结构,用于解释如果模板参数用作函数参数类型则存在的特殊推导规则。

由于不存在这样的类型,因此无法使用该类型声明函数指针,证明无法实现通过函数指针的间接完美转发。