This rule is part of MISRA C++:2023.
Usage of this content is governed by Sonar’s terms and conditions. Redistribution is
prohibited.
Rule 9.5.2 - A for-range-initializer shall contain at most one function call
[stmt.ranged]
Category: Required
Analysis: Decidable,Single Translation Unit
Amplification
The for-range-initializer occurs within the range-based for statement:
for ( for-range-declaration : for-range-initializer ) statement
For the purposes of this rule, the following are also considered to be function calls:
- Any expression creating an object of
class type; and
- Any use of an overloaded operator.
Rationale
Compliance with this rule will avoid the undefined behaviour related to object lifetime violations when the for-range-initializer of a
range-based for statement creates a temporary object.
The range-based for statement is defined within the C++ Standard as being equivalent to:
{
auto && __range = for-range-initializer;
auto __begin = begin-expr; // Uses __range
auto __end = end-expr;
for ( ; __begin != __end; ++__begin)
{
for-range-declaration = *__begin;
statement
}
}
Even though lifetime extension through __range will extend the lifetime of the outermost temporary object of the
for-range-initializer, it will not extend the lifetime of an intermediate temporary. The rules for temporary lifetime extension are subtle
and it is easy to accidentally trigger undefined behaviour by accessing a temporary object after its lifetime has ended (see
M23_360: MISRA C++ 2023 Rule 6.8.1).
Creating a temporary object containing a range requires a function call, and only a second call can result in creating a reference to or into it.
Therefore, allowing no more than one function call eliminates the risk in a way that is decidable at the expense of prohibiting some non-problematic
cases. Defining a variable holding the value of the desired for-range-initializer and using that variable will always be compliant with this
rule.
Note: these lifetime issues with range-for statements have been resolved from C++23.
Example
extern std::vector < std::string > make();
void f()
{
for ( char c: make().at( 0 ) ) // Non-compliant - two function calls
{
}
}
void g()
{
auto range = make().at( 0 ); // Note that auto && would dangle
for ( char c: range ) // Compliant - no call when using named range
{
}
}
void h()
{
for ( auto s: make() ) // Compliant - single function call
{
}
}
The following shows an example that has no undefined behaviour, but which includes non-compliant cases as a consequence of preferring a
decidable check:
std::vector< std::string > make( std::string_view );
void bar( std::string s )
{
for ( auto e : make( s ) ) // Non-compliant - call to 'make' and an
{ // implicit conversion to std::string_view
}
auto r = make( s );
for ( auto e : r ) // Compliant version of the above
{
}
}
Copyright The MISRA Consortium Limited © 2023