Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Enabling MFC Collections to Work in Range-based for Loops

0.00/5 (No votes)
30 Oct 2014 2  
MFC Collection Utilities is a small open-source library that enables you to use any MFC collection with range-based for loops.

This article shows how to enable MFC collections to work with range-based for loops in C++11. MFC is probably not the primary framework of choice for new projects, but there are lots of MFC based applications that have to be maintained. Enabling MFC collections to work with range-based for loops is a plus in modernizing your legacy code. An open-source library available on Codeplex, called MFC Collection Utilities, provides this functionality.

Introduction

The standard way of iterating over the elements of a MFC collection is to use an index.

CStringArray strarr;
strarr.Add("one");
strarr.Add("two");
strarr.Add("three");
for(INT_PTR i = 0; i < strarr.GetSize(); ++i)
{
   CString temp = strarr.GetAt(i); // could also be CString const &
}

The index here is not important. It’s just a temporary that allows you to access the element.

The same is valid for C++ standard containers with random-access.

std::vector<int> v = {1, 2, 3};
for(size_t i = 0; i < v.size(); ++i)
{
   int value = v[i];
}

Of course, the general way to iterate over the elements of a C++ standard container is to use iterators.

std::vector<int> v = {1, 2, 3};
for(std::vector<int>::iterator i = std::begin(v); i != std::end(v); ++i)
{
   int value = *i;
}

Regardless of whether it's a numeric value or an iterator, in most loops it is not necessary. Many languages provide foreach loop semantics that do not require an index or an interator. This has been added to C++11 and is known as range-based for loops.

With range-based for loops, the last example can be written as follows:

std::vector<int> v = {1, 2, 3};
for(auto value : v)
{
   // do something with value
}

However, if we apply the same on the first example, with the CStringArray we get errors.

CStringArray strarr;
strarr.Add("one");
strarr.Add("two");
strarr.Add("three");
for(auto const & temp : strarr)
{
}
1>error C3312: no callable 'begin' function found for type 'CStringArray'
1>error C3312: no callable 'end' function found for type 'CStringArray'

The reason is range-based for loops is just syntactic sugar and the compiler transforms it into something else. The general expression...

for ( range_declaration : range_expression ) loop_statement

...is transformed into the following:

{
   auto && __range = range_expression ; 
   for (auto __begin = begin_expr, __end = end_expr; 
        __begin != __end;
        ++__begin)
   {
      range_declaration = *__begin;
      loop_statement 
   } 
}

The rules for begin_expr and end_expr vary depending on the type of the range:

  • for C-like arrays: __range and __range + __bound
  • for a class type with begin() or end() members: __range.begin() and __range.end()
  • for others: begin(__range) and end(__range)

This explains the error for the CStringArray example. There is no begin(CStringArray) and end(CStringArray) available in MFC.

Enabling MFC Collections to Work in Range-based for Loops

To enable the MFC collections to work with range-based for loops, we need two things:

  • collection iterators that implement operator++, operator* and operator!=
  • non-member functions begin() and end() for each collection that returns the appropriate begin and end iterators

For the CStringArray collection, this can be implemented as follows:

class CStringArrayIterator
{
public:
   CStringArrayIterator(CStringArray& collection, INT_PTR const index):
      m_index(index),
      m_collection(collection)
   {
   }

   bool operator!= (CStringArrayIterator const & other) const
   {
      return m_index != other.m_index;
   }

   CString& operator* () const
   {
      return m_collection[m_index];
   }

   CStringArrayIterator const & operator++ ()
   {
      ++m_index;
      return *this;
   }

private:
   INT_PTR        m_index;
   CStringArray&  m_collection;
};

inline CStringArrayIterator begin(CStringArray& collection)
{
   return CStringArrayIterator(collection, 0);
}

inline CStringArrayIterator end(CStringArray& collection)
{
   return CStringArrayIterator(collection, collection.GetCount());
}

With this defined, if you run the first example in this article with the CStringArray, it will work. You can put breakpoints in the overloaded operators and see how they are called as you iterate over the elements of the container.

However, if you execute the following code, it will again trigger an error:

void Func(CStringArray const & strarr)
{
   for(auto const & temp : strarr)
   {

   }
}

The problem is that the type of the range is now const CStringArray and there is no begin() and end() function for that. So you have to also define constant iterators and begin() and end() functions that work with constant collections and return the appropriate iterators.

MFC Collection Utilities Library

MFC Collection Utilities is a small open-source library (available on Codeplex) that enables the use of all MFC containers with range-based for loops.

MFC provides a series of template and non-template collections and the library defines the appropriate iterators and begin() and end() functions for all of them (both for constant and non-constant types). The library consists of a single header file called mfciterators.h that you have to include in your source files where you want to use the MFC collections in range-based for loops.

The library requires Visual Studio 2012 or a newer version. VS2012 is the first version of Visual Studio that included a C++ compiler with support for range-based for loops.

#include "mfciterators.h"

void func(CStringArray const & arr)
{
   for(auto const & str : arr)
   {
      // do something with str
   }
}

There are three types of collections provided by MFC: arrays, lists and maps. For maps, the library provides access to the content through a key-value pair that has two fields: key and value.

Supported template collections:

Arrays Lists Maps
CArray CList CMap
CTypedPtrArray CTypedPtrList CTypedPtrMap

Supported non-template collections:

Arrays Lists Maps
CObArray CObList CMapPtrToWord
CByteArray CPtrList CMapPtrToPtr
CDWordArray CStringList CMapStringToOb
CPtrArray   CMapStringToPtr
CStringArray   CMapStringToString
CWordArray   CMapWordToOb
CUIntArray   CMapWordToPtr

Notice that some of the template collections are actually type-safe wrappers for objects of a non-template collection type and cannot be used with other classes.

  • CTypedPtrArray for CPtrArray and CObArray
  • CTypedPtrList for CPtrList
  • CTypedPtrMap for CMapPtrToPtr, CMapPtrToWord, CMapWordToPtr, and CMapStringToPtr

Examples

Arrays:

CStringArray arr;
arr.Add("this");
arr.Add("is");
arr.Add("a");
arr.Add("sample");

for(auto & s : arr)
{
   s.MakeUpper();
}
CArray<int> arr;
arr.Add(1);
arr.Add(2);
arr.Add(3);
arr.Add(4);

for(auto const n : arr)
{
   std::cout << n << std::endl;
}

Lists:

class CFoo
{
public:
   int value;

   CFoo(int const v): value(v) {}
};

CTypedPtrList<CPtrList, CBar*> ptrlist;
ptrlist.AddTail(new CFoo(1));
ptrlist.AddTail(new CFoo(2));
ptrlist.AddTail(new CFoo(3));

for(auto & o : ptrlist)
   o->value *= 2;
CList<int> list;
list.AddTail(1);
list.AddTail(2);
list.AddTail(3);

auto const & clist = list;
for(auto const n : clist)
{
   std::cout << n << std::endl;
}

Maps:

CMap<int, int, CString, CString> map;

map.SetAt(1, "one");
map.SetAt(2, "two");
map.SetAt(3, "three");

for(auto & kvp : map)
{
   kvp.value.MakeUpper();
}

for(auto const & kvp : map)
{
   CString temp;
   temp.Format("key=%d, value=%s", kvp.key, kvp.value);
}
CTypedPtrMap<CMapWordToPtr, WORD, CFoo*> map;

map.SetAt(1, new CFoo(1));
map.SetAt(2, new CFoo(2));
map.SetAt(3, new CFoo(3));

// do something with map

for(auto & kvp : map)
{
   delete kvp.value;
}

Downloading and Using the Library

The library is available on Codeplex and the latest version can be downloaded from here.

To simplify deployment to a project, a nuget package is available. You can download it and add it to your project directly from Visual Studio using the NuGet Package Manager.

Conclusions

The range-based for loops in C++11 provide foreach semantics enabling you to iterate over ranges/collections without using an index or iterator. MFC collections do not provide the appropriate iterators and functions required for the range-based for loops, but they can easily be created.

MFC Collection Utilities is a small open-source library, consisting of a single header, that enables you to use any MFC collection with range-based for loops in Visual Studio 2012 or newer.

History

  • 30th October, 2014: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here