Main Content

AUTOSAR C++14 Rule A8-4-6

"forward" parameters declared as T && shall always be forwarded

Since R2021a

Description

Rule Definition

"forward" parameters declared as T && shall always be forwarded.

Rationale

Because rvalue references cannot bind to lvalues, functions that enable the use of move semantics by using rvalue references in their signature do not accept lvalues. This issue is resolved by using an rvalue reference to a nonconst template type object, which is called a "forward" parameter. These parameters can bind to both rvalues and lvalues while preserving their cv qualifications and value categories. "Forward" parameters are useful when you want to forward a value to a destination object or function by using the function std::forward.

When you declare a function template by using a "Forward" parameter, do not use the parameter in any operations. Because "Forward" parameters can bind to both lvalues and rvalues, using them in an operation might corrupt their cv qualifications and value categories. Forward these parameters directly to the destination by using std::forward without using them in an operation.

Polyspace Implementation

Polyspace® flags a "Forward" parameter in the definition of a function template or a Lambda expression if any of these conditions are true:

  • A "Forward" parameter is not forwarded to a destination by using std::forward.

  • An operation other than forwarding is performed on the "Forward" parameter or on a member object of it.

Polyspace ignores the templates and Lambda expressions that remain unused in your code.

Troubleshooting

If you expect a rule violation but Polyspace does not report it, see Diagnose Why Coding Standard Violations Do Not Appear as Expected.

Examples

expand all

#include<string>
#include<vector>
#include <iostream>

struct intWrapper {
	intWrapper(int&& n) { std::cout << "rvalue overload, n=" << n << "\n"; }
	intWrapper(int& n)  { std::cout << "lvalue overload, n=" << n << "\n"; }
};
struct floatWrapper {
	floatWrapper(double&& n) { std::cout << "rvalue overload, n=" << n << "\n"; }
	floatWrapper(double& n)  { std::cout << "lvalue overload, n=" << n << "\n"; }
};

class mixedNumerical {
public:
	template<class T1, class T2, class T3>
	mixedNumerical(T1&& t1, T2&& t2, T3&& t3) :   //violation on T3    
	a1_{std::forward<T1>(t1)},
	a2_{std::forward<T2>(t2)},
	a3_{std::forward<T3>(t3)}
	{
	}

private:
	intWrapper a1_, a2_;
	floatWrapper	a3_;
};


template<class T, class... U>
std::unique_ptr<T> unique_ptr_factory(U&&... u)
{
	return std::unique_ptr<T>(new T(std::forward<U>(u)...));    
}

int main()
{
	auto p1 = unique_ptr_factory<intWrapper>(2); // rvalue
	int i = 1;
	auto p2 = unique_ptr_factory<intWrapper>(i); // lvalue

	std::cout << "mixedNumerical\n";
	double lvalue = 2.2;
	auto t = unique_ptr_factory<mixedNumerical>(2, i, 2.2);// rvalue
	auto t2 = unique_ptr_factory<mixedNumerical>(2, i, lvalue);// lvalue
}


This example shows the implementation of a flexible interface to the function unique_ptr_factory by using a "forward" parameter pack. This function accepts the "forward" parameter pack, and then forwards the parameters to their respective constructors by using std::forward. The constructors are overloaded to accept both rvalues and lvalues. As a result, the function unique_ptr_factory produces unique_ptr to intWrapper type objects and mixedNumerical type objects while requiring minimal overloading. This use of "forward" is compliant with this rule because the "forward" parameters are forwarded to their destination by using std::forward. Because no other operation is performed on them, their cv qualification and value categories are preserved.

#include<string>
#include<vector>
void task(int i);
template<typename T>
T NoncompliantTemplate(T&& arg)    // Noncompliant
{
	return arg;       // Noncompliant
}

auto NoncompliantLambda = [](auto&& truc) {    // Noncompliant
	return truc;                         // Noncompliant
};
template<typename T>
T ReturnStaticCast(int&& i) //Compliant: not a template parameter.
{
	return static_cast<T>(i);
}
template<typename T>
void ConstArg(const T&& t)      // Compliant: const
{}

template<typename T>
void UnusedArg(T&& t)              // Noncompliant
{}

template<typename T>
void UnnamedArg(T&& )              // Noncompliant
{}
template<typename T>
void usage(T&& t1, T&& t2)
{
	if (t1==t2)                       // Noncompliant
	{
		task(std::forward<T>(t1));
	}
	else
	{
		task(std::forward<T>(t1));
		task(std::forward<T>(t2));
	}
}
class intWrapper
{
public:
	int m;
};

template<typename T>
void CheckForward(T&& t)
{
	if (t.m != 0)                        // Noncompliant
	{
		UnusedArg(std::forward<T>(t));
	}
}
auto CompliantLambda = [](auto&& truc) {    // Compliant
	return NoncompliantLambda(std::forward<decltype(truc)>(truc));
};
template<typename T>
T NoninstantiatedTemplate(T&& arg)    // Not checked
{
	return arg;       // Not checked
}
void foo(){
	int i;
	intWrapper C;
	C.m = i;
	NoncompliantTemplate(i);
	CheckForward(std::move(C));
	usage(i,C.m);
	UnnamedArg(i);
	CompliantLambda(i);
}

This example shows use of "forward" parameters that are not compliant with this rule.

  • Polyspace flags the nonconst T&& parameter arg of the template NoncompliantTemplate because this "forward" parameter is not forwarded to a destination by using std::forward. The parameter is flagged in the declaration and the return statement.

  • Polyspace flags the nonconst auto&& parameter truc of the Lambda expression NoncompliantLambda because this "forward" parameter is not forwarded to a destination by using std::forward. The parameter is flagged in the declaration and the return statement.

  • Polyspace does not flag int&& argument i of the template ReturnStaticCast because this argument is not a nonconst template type rvalue reference. For the same reason, Polyspace does not flag the argument of ConstArg.

  • Polyspace flags the nonconst template type rvalue reference argument t of the template UnusedArg because this "forward" parameter is not forwarded to a destination by using std::forward.

  • Polyspace flags the argument of the template UnnamedArg because the "forward" parameter is unnamed and it cannot be forwarded by using std::forward.

  • Polyspace flags the parameters t1 and t2 in the statement if (t1==t2) in the template usage because these "forward" parameters are used in an operation before they are forwarded by using std::forward. This checker is also raised on t in the statement if (t.m != 0) in the template CheckForward because a member of the "forward" parameter t is accessed.

  • Polyspace does not check the template NoninstantiatedTemplate because this template is unused in the code.

Check Information

Group: Declarators
Category: Required, Automated

Version History

Introduced in R2021a