Home > database >  What language rules governs that `T&&` in a templated array-by-&& function argument is *not* a forwa
What language rules governs that `T&&` in a templated array-by-&& function argument is *not* a forwa

Time:03-29

A static analysis tool I'm using prompts me that I need to std::forward the argument of the following function down the call chain:

template<typename T, std::size_t size>
constexpr void f(T (&& arr)[size]) { /* linter: use std::forward on 'arr' here */ }

as it identifies the function parameter type as a forwarding reference.

A simple test on several compilers show, however, that this is not a forwarding reference. The following example

void g() {
    int a[3]{1, 2, 3};
    f(a);  // #1
}

is rejected at #1 (DEMO), explicitly pointing our that the function parameter is an rvalue reference:

 error: cannot bind rvalue reference of type 'int (&&)[3]' to lvalue of type 'int [3]'

Question

  • What language rules governs that T&& in a templated array-by-&& function argument as in f above is not a forwarding reference?

CodePudding user response:

The relevant section is [temp.deduct.call]/1 [emphasis mine]:

Template argument deduction is done by comparing each function template parameter type (call it P) that contains template-parameters that participate in template argument deduction with the type of the corresponding argument of the call (call it A) as described below. If removing references and cv-qualifiers from P gives std::initializer_list<P′> or P′[N] for some P′ and N and the argument is a non-empty initializer list ([dcl.init.list]), then deduction is performed instead for each element of the initializer list independently, taking P′ as separate function template parameter types P′i and the i:th initializer element as the corresponding argument. [...]

Meaning [temp.deduct.call]/3 does not apply for a P that was originally an array reference type (once it reaches /3, /3 applies per element).

However, if we return to the section above we may note that it comes with the restriction

[...] and the argument is a non-empty initializer list [...]

meaning a call, for OP's example function f, like

f({1, 2, 3});  // [temp.deduct.call]/1 applies

But what about the case where the argument is not an initializer list, but an array?

int arr[3]{1, 2, 3};
f(arr);

[temp.deduct.call]/2 have special rules for arrays, but it does not apply as P is a reference type, meaning we do end up at [temp.deduct.call]/3:

If P is a cv-qualified type, the top-level cv-qualifiers of P's type are ignored for type deduction. If P is a reference type, the type referred to by P is used for type deduction.

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.

The function parameter in f, however, is an rvalue reference to T[size], which is not a template parameter. This governs the case for when the argument is not an initializer list (and arguably also that case).

  • Related