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 inf
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 itA
) as described below. If removing references and cv-qualifiers fromP
givesstd::initializer_list<P′>
orP′[N]
for someP′
andN
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, takingP′
as separate function template parameter typesP′i
and thei
: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).