Main Content

CERT C++: CTR58-CPP

Predicate function objects should not be mutable

Since R2022a

Description

Rule Definition

Predicate function objects should not be mutable.1

Polyspace Implementation

The rule checker checks for Function object that modifies its state.

Examples

expand all

Issue

This issue occurs when the following conditions are true:

  • You use a STL algorithm that takes a predicate function object. For a full list of STL algorithms, see Algorithms library.

  • The state of the function object is modified, where the state is one of the following:

    • The this pointer if the predicate object is an instance of a class that defines a function call operator operator().

    • A captured-by-copy value if the predicate object is a lambda function. A lambda function that captures values by reference is compliant.

A function object state is modified if it is written to or if a non-const method is called on it. For example, in this code snippet, non-const method increment() modifies the state elemPosition of operator() :

#include <functional>
#include <vector>
#include <algorithm>

class myPred : public std::unary_function<int, bool> {
  public:
    myPred(): elemPosition(0) {}
    void increment() {++elemPosition;} // Non-const method
    bool operator()(const int&) // function call operator
    {
        increment(); // method called on state elemPosition
        return elemPosition == 3;
    }
  private:
    size_t elemPosition;
};

void func(std::vector<int> v) {
    std::remove_if(v.begin(), v.end(), myPred()); // Noncompliant
}
Polyspace® flags function objects that modify their state even if the function object is not a predicate.

Polyspace does not flag function objects that are wrapped in these:

  • std::ref

  • std::cref

  • std::function

  • std::bind

  • std::not1

Risk

Predicate function objects take a single argument and use that argument to return a value that is testable as a boolean (true or false). Standard Template Library (STL) algorithms that accept a predicate function object may, depending on the algorithm implementation, make a copy of the function object. The invocation of the copy might cause unexpected results.

For example, suppose that you use an algorithm to remove one element from a list. To determine the element to remove, the algorithm uses a predicate function object that modifies a state related to the function object identity each time the function object is called. You might expect the predicate object to return true only once. If the algorithm makes a copy of the predicate object, the state of the copy is also modified and the copy returns true a second time, removing a second element from the list.

Fix

To avoid unexpected results, consider one of the following:

  • Wrap the predicate function object in a std::reference_wrapper<T> before you pass it to the algorithm. If the wrapper object is copied, all copies refer to the same underlying predicate function object.

    For example, in this code snippet, predicate function object myObj is wrapped in std::ref when passed to std::remove_if.

    class predObj {
        // Defines function object that modifies its state
    };
    void func() {
        std::vector<int> v{0, 1, 2, 3, 4, 5};
        //
        predObj myObj;
        v.erase(std::remove_if(v.begin(), v.end(), std::ref(myObj)), v.end());
        //....
    }

  • Implement a const function call operator that does not modify the state of the predicate object. For example, in this code snippet, predModifies defines a call operator that modifies elemPosition before checking the equality whereas predDoesNotModify checks only the equality of var without modifying it.

    #include <functional>
    #include <vector>
    #include <algorithm>
    
    class predModifies : public std::unary_function<int, bool>
    {
    public:
    predModifies() : elemPosition(0) {}
    bool operator()(const int &) const { return (++elemPosition) == 3; }
    //call operator modifies elemPosition
    private:
    mutable size_t elemPosition;
    };
    
    class predDoesNotModify: public std::unary_function<int, bool>
    {
    public:
    bool operator()(const int& var) const { return var == 3; }
    //call operator does not modify state of object
    };
    

Example — Function Object that Modifies Its State
#include <functional>
#include <vector>
#include <algorithm>


void lambdaDoesNotModify(std::vector<int> v)
{
  int elemPosition = 0;
  std::remove_if(v.begin(), v.end(),
    [elemPosition](const int & element) mutable {  //Compliant
      return elemPosition == 0 && element == 3;
    });
}

void lambdaCaptureByRef(std::vector<int> v)
{
  int elemPosition = 0;
  std::remove_if(v.begin(), v.end(),
    [&elemPosition](const int & i) { // Compliant
      return ++elemPosition == 3;
    });
}

class nonConstPred : public std::unary_function<int, bool>
{
  public:
    nonConstPred() : elemPosition(0) {}
    void increment() { ++elemPosition; }
    bool operator()(const int &) 
    {
      increment(); 
      return elemPosition == 3;
    }
  private:
    size_t elemPosition;
};

void myFunc(std::vector<int> v)
{
  std::remove_if(v.begin(), v.end(), nonConstPred()); // Noncompliant
}

In this example, the use of predicate function object nonConstPred() inside function myFunc is not compliant. The call operator inside class nonConstPred uses a non-const method increment() that modifies elemPosition, the state of the predicate. If the algorithm std::remove_if makes a copy of the predicate function object, there might be two instances of elemPosition == 3 being true, which might cause unexpected results.

The use of a lambda function as predicate function object in function lambdaDoesNotModify is compliant because the state of the function object is not modified. Similarly, the lambda function in lambdaCaptureByRef is compliant because the state of the function object is captured by reference.

Correction — Wrap the Function Object in std::ref
#include <functional>
#include <vector>
#include <algorithm>


void lambdaDoesNotModify(std::vector<int> v)
{
  int elemPosition = 0;
  std::remove_if(v.begin(), v.end(),
    [elemPosition](const int & element) mutable {  //Compliant
      return elemPosition == 0 && element == 3;
    });
}

void lambdaCaptureByRef(std::vector<int> v)
{
  int elemPosition = 0;
  std::remove_if(v.begin(), v.end(),
    [&elemPosition](const int & i) { // Compliant
      return ++elemPosition == 3;
    });
}

class nonConstPred : public std::unary_function<int, bool>
{
  public:
    nonConstPred() : elemPosition(0) {}
    void increment() { ++elemPosition; }
    bool operator()(const int &) 
    {
      increment(); 
      return elemPosition == 3;
    }
  private:
    size_t elemPosition;
};

void myFunc(std::vector<int> v)
{
  nonConstPred myPred;
  std::remove_if(v.begin(), v.end(), std::ref(myPred)); // Compliant
}

One possible correction is to wrap the function object in std::ref. All copies of the wrapper object refer to the same underlying function object and there is only one instance of the state elemPosition.

Check Information

Group: Rule 04. Containers (CTR)

Version History

Introduced in R2022a


1 This software has been created by MathWorks incorporating portions of: the “SEI CERT-C Website,” © 2017 Carnegie Mellon University, the SEI CERT-C++ Web site © 2017 Carnegie Mellon University, ”SEI CERT C Coding Standard – Rules for Developing safe, Reliable and Secure systems – 2016 Edition,” © 2016 Carnegie Mellon University, and “SEI CERT C++ Coding Standard – Rules for Developing safe, Reliable and Secure systems in C++ – 2016 Edition” © 2016 Carnegie Mellon University, with special permission from its Software Engineering Institute.

ANY MATERIAL OF CARNEGIE MELLON UNIVERSITY AND/OR ITS SOFTWARE ENGINEERING INSTITUTE CONTAINED HEREIN IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.

This software and associated documentation has not been reviewed nor is it endorsed by Carnegie Mellon University or its Software Engineering Institute.