Main Content

MISRA C++:2023 Rule 8.1.2

Variables should be captured explicitly in a non-transient lambda

Since R2024b

Description

Rule Definition

Variables should be captured explicitly in a non-transient lambda.

Rationale

A lambda is nontransient if any of these conditions are true:

  • The lambda is invoked anywhere other than immediately after its definition.

  • The lambda is passed to a function that stores it.

If a nontransient lambda captures variables implicitly, then the dependencies of these lambdas can be difficult to determine when they are invoked. Consider the nontransient lambda sum:

void foo() {
	int x, y, z;

	auto const sum = [&]() {
		return x + y;
	};
	//...
	int val = sum(); //Noncompliant
}
The lambda captures the variables x and y implicitly. When this lambda is invoked later in the code, it is unclear which variables it uses to calculate val. To avoid such confusion, use explicit capture when defining nontransient lambdas. Explicit capture helps identify which objects are captured and can help identify dangling pointers or dangling references when the lambda is invoked.

Polyspace Implementation

Polyspace® reports a violation of this rule if a nontransient lambda captures variables implicitly, either by value or by reference. Nontransient lambdas can include:

  • Lambdas that are not invoked immediately after their definition.

  • Lambdas that are returned by a function.

  • Lambdas that are passed to a function that stores it. Polyspace assumes a lambda is stored by a function if:

    • A lambda is passed to a function as an argument and then assigned to a container from the standard template library or to a variable.

    • A lambda is passed to a function defined in a different translation unit.

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

This example shows lambdas that capture object implicitly.

  • In nonstoring_lambdas::caller(), lambdas are passed to the function templates foo() and bar(). Because these function templates do not assign the passed lambda to any object, Polyspace assumes that the lambdas are not stored. Because the lambdas are not stored, they are not nontransient lambdas and the implicit capture does not violate this rule.

  • In storing_lambda::foo(), a lambda is passed to the function storing_lambda::DoSomething(), which is defined in a different source file. Because the function is defined in a different translation unit, Polyspace assumes that the function stores the lambda, making the passed lambda nontransient. The implicit capture of the passed lambda violates this rule.

  • In storing_lambda::bar(), lambdas are stored when they are passed to storing_lambda::myclass::addfunc(). Passing a lambda with implicit capture to storing_lambda::myclass::addfunc() violates this rule.

  • The function storing_lambda::foo() returns a lambda. Because the returned lambda is nontransient and captures variables implicitly, Polyspace report a violation.

#include <vector>
#include <functional>
#include <iostream>


namespace nonstoring_lambdas {
	template< typename Iter, typename myLambda >
	bool foo(Iter b, Iter e, myLambda L)
	{
		for(Iter it = b; it != e; ++it)
		{
			if(L(*it))
			{
				return true;
			}
		}
		return false;
	}

	template< typename myContainer, typename myLambda >
	bool bar(myContainer const &c, myLambda L)
	{
		return foo(std::begin(c), std::end(c), L);
	}

	void caller(std::vector< size_t > const &v, size_t i)
	{
		bool b1 = foo(v.cbegin(), v.cend(),
		[&](size_t elem) {
			return elem == i;
		});                   // Compliant
		bool b2 = bar(v,
		[&](size_t elem) {
			return elem == i;
		});                   // Compliant
	}
}

namespace storing_lambda {
	class myclass {
	public:
		std::vector< std::function< void (double) > > FuncList;
		template< typename myLambda >
		void addfunc(myLambda L)
		{
			FuncList.push_back(L);
		}
	};

	void DoSomething(std::function< myclass() >);


	auto foo()
	{
		myclass s;
		DoSomething([&]() {
			return s;
		});                             // Noncompliant
		return [ = ]() {
			return s;
		};                               // Noncompliant
	}

	void bar(myclass s, std::ostream &os)
	{
		s.addfunc([&](double num)               // Noncompliant
		{
			os << num;
		});
		s.addfunc([&os](double num)             // Compliant
		{
			os << num;
		});
		s.addfunc([](double num)                // Compliant
		{
			std::cout << num;
		});
	}
}

Check Information

Group: Expressions
Category: Advisory

Version History

Introduced in R2024b