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);
}
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)
{
}
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)
{
}
}
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));
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