More Pythonic Collection Functions with C++11

Last updated: 6-4-2015

  1. More Pythonic Collection Functions with C++11
    1. Overview
    2. Examples
      1. Range Based For Loop
      2. Test Elements in Collection
      3. Find Element in Collection
      4. Modify Every Element in Collection
      5. Modify Collection
      6. Copy Collection
      7. Modify Every Element in a Collection with Some Common Code
      8. Move a container holding unique_ptr types

Overview

What is Pythonic programming in C++? Everybody may have a different opinion, but some of the advantages are:
This document only shows some examples of collections and lambdas.

When looking at a loop in a function that is operating on a collection, some questions to the programmer reading the code may be:
It is more difficult to see which of these is being performed if all loops look the same.

If there are more variables and loops in functions, the code is more difficult to quickly understand.  Hopefully the examples illustrate these points.

This document only describes a few functions, and may of them were around before C++11. There are many other functions in <algorithms>. (Search the web for "std algorithms")

Examples

Range Based For Loop

Old Code:
std::vector<PersonNames> names = getNames();
for(int i=0; i<names.size(); i++)
{
names[i] += ';';
}
New Code:
std::vector<PersonNames> names = getNames();
for(auto const &name : names)
{
name += ';';
}
In the new code, the iterator is removed which removes more clutter from the source code. Since the 'i' variable is removed, there is no way that the iterator is modified inside the loop, or has out of range bugs. It could also lead to better optimizations for the compiler. The range based for also works for standard C arrays, and also custom containers as long as begin() and end() are defined.

Test Elements in Collection

Old Code:
bool ComponentTypes::anyComponentsDefined() const
{
    bool defined = false;
for(auto const &name : getComponentNames())
{
if(getComponentType(name.c_str()) != CT_Unknown)
{
defined = true;
break;
}
}
return defined;
}
New Code:
bool ComponentTypes::anyComponentsDefined()
{
auto const &names = getComponentNames();
return std::any_of(names.begin(), names.end(),
[=](std::string const &name) -> bool
{return(getComponentType(name.c_str()) != CT_Unknown);} );
}
This example checks if any elements in a vector have a certain type. The getComponentNames() function returns a "std::vector<std::string>".
The "[=]" text indicates the start of a Lambda function. The "=" indicates that a capture by value of some parameters is needed for the code within the "{}".  In this case, the "this" pointer is needed for the getComponentType() function. In this example, the"this" keyword could have been used instead of the "=". The "-> bool" indicates that the return type of the lambda is a boolean.

Some Test Functions:
    all_of, any_of, none_of

Find Element in Collection

Some of this has been around before C++11.

Old Code:
std::vector<std::string> packages = getPackages();
bool found = false;
for(int i=0; i<packages.size(); i++)
{
if(packages[i].mName.compare(name) == 0)
found = true;
break;
}
Pre C++11 Code:
std::vector<std::string> packages = getPackages();
bool found = std::find(packages.begin(), packages.end(), name) != packages.end());
More Advanced Example with C++11 Code:
auto const &supplierIter = std::find_if(packages.begin(), packages.end(),
[nodeName](Package const &pkg) -> bool
{ return(pkg.getPkgName().compare(nodeName) == 0); });

Another Advanced Example:

auto iter = std::lower_bound(mTypes.begin(), mTypes.end(), name,
        [](ModelType const *mod1, char const * const mod2Name) -> bool
        { return(compareStrsUpper(mod1->getName().c_str(), mod2Name)); } );

The lower_bound performs a binary search, and shows an interesting example where part of the class is compared to the iterated class reference.
Some Find Functions:
    find_if, find_if_not, find_first_of, search

Find Element Notes

Modify Every Element in Collection

Loop Code:
for(auto &fn : incs)
{
FilePath fp;
fp.getAbsolutePath(fn.c_str(), FP_Dir);
fn = fp.pathStdStr();
}
New Lambda Code:
for_each(incs.begin(), incs.end(), [](std::string &fn)
{
    FilePath fp;
    fp.getAbsolutePath(fn.c_str(), FP_Dir);
    fn = fp.pathStdStr();
    });
There is not much advantage to using for_each over range-based-for, except that it indicates that each element will be examined or modified, and a lambda can be used.

Modify Collection

Old Code:
    static void removeLib(char const * const libName, std::set<std::string> &libDirs)
	{
	for(int i=0; i<libDirs.size(); i++)
	    {
	    if(libDirs.compare(libName) == 0)
	        clear(libDirs.begin()+i);
	    }
	}
    
New Code:
    static void removeLib(char const * const libName, std::set<std::string> &libDirs)
	{
	libDirs.erase(std::remove_if(libDirs.begin(), libDirs.end(),
	    [=](std::string &str){ return(str.compare(libName) == 0); }), libDirs.end());
	}
    
There are two common problems that can occur using remove_if. They can result in duplicate values in the vector.

Sort example:

std::sort(mTypes.begin(), mTypes.end(), [](ModelType const *type1,
    ModelType const *type2) -> bool
    {
    return compareStrsUpper(type1->getName().c_str(), type2->getName().c_str());
    });

These functions have been around before C++11. They sort and reverse the order of the collections.

    std::sort(mPackageNames.begin(), mPackageNames.end());
    std::reverse(clumpNames.begin(), clumpNames.end());

The "=" sign in the "[=]" characters indicates that a capture is needed. In this case, the libName parameter is needed by the lambda. The sort example shows that it is possible to sort an object even if the objects don't have a comparison operator.

Copy Collection

New Code:
    std::vector<std::string> headers;
    std::copy_if(possibleHeaders.begin(), possibleHeaders.end(),
std::back_inserter(headers),
        [](std::string const &header) { return isHeader(header.c_str()); });
Old Code:
    std::vector<std::string> headers;
for(int i=0; i<possibleHeaders.size(); i++)
{
if(isHeader(possibleHeaders[i].c_str()))
{
headers.push_back(possibleHeaders[i]);
}
}
This function copies only certain strings from one vector to another based on the results of the isHeader() function. The back_inserter will perform a push_back at the end of the target vector.

To copy an entire collection, use:
    std::copy(sourceDirs.begin(), sourceDirs.end(), destDirs.begin());
For vectors, use an std::back_inserter.

Modify Every Element in a Collection with Some Common Code

This is used to remove duplicate code by moving the duplicate code into a functor. A small amount of custom code is supplied by a lambda.

Create a functor:

    template <typename Func> void processInts(std::vector const &intCollection,
        Func procFunc)
        {
	for(auto const &intVal : intCollection)
            {
            // Some common special code can be placed here.
            procFunc(intVal);
            }
        }
    
Invoke the functor:
    void f()
        {
	std::vector numbers = { 3, 6, 9 };
        processInts(numbers, [](int val) { printf("%d\n", val); });
        }
If the lambda modifies values, then add mutable:
    void f()
        {
	std::vector numbers = { 3, 6, 9 };
// This modifies the values in "numbers"
processInts(numbers, [](int &val) mutable { printf("%d\n", val++); }); }

And if the lambda modifies captured variable, then add "&":

    void f()
        {
	std::vector numbers = { 3, 6, 9 };
int x = 0;
// This modifies "x".
processInts(numbers, [&x](int val) { printf("%d\n", x+=val); }); }

For a complete example, see oovaide/oovCommon/IncludeMap.cpp.

Move a container holding unique_ptr types

The following defines a move constructor to move an object that has a container that holds std::unique_ptr values. This allows performing a very fast move inside of a lock between multiple threads. An std::move is performed both on the container, and between two objects that encapsulate the container.
        class DebugResult
            {
            public:
                DebugResult(DebugResult &&src)
                    {
                    if(this != &src)
                        {
                        clear();
                        mChildResults = std::move(src.mChildResults);
                        }
                    }

            private:
                std::deque> mChildResults;
            }
        DebugResult srcResult;
        DebugResult res(std::move(srcResult));
    

For a complete example, see oovEdit/Debugger.cpp and DebugResult.h/cpp.