Click here to Skip to main content
13,833,081 members
Click here to Skip to main content
Add your own
alternative version

Stats

12K views
10 bookmarked
Posted 30 Oct 2014
Licenced CPOL

Enabling MFC collections to work in range-based for loops

, 30 Oct 2014
Rate this:
Please Sign up or sign in to vote.
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 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 following:

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 sugare 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 the each collection that returns the appropriate begin and end iterators

For the CStringArray collection this can be implemented as following:

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 brakpoints 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 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 appropriat 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Marius Bancila
Architect Visma Software
Romania Romania
Marius Bancila is the author of Modern C++ Programming Cookbook and The Modern C++ Challenge. He used to be a Microsoft MVP for VC++ and later Visual Studio and Development Technologies for 11 years. He works as a system architect for Visma, a Norwegian-based company. He is mainly focused on building desktop applications with VC++ and VC#. He keeps a blog at http://www.mariusbancila.ro/blog, focused on Windows programming. He is the co-founder of codexpert.ro, a community for Romanian C++ programmers. You can follow Marius on Twitter at @mariusbancila.

You may also be interested in...

Comments and Discussions

 
QuestionNice! Pin
Mark Richards15-Oct-15 6:20
memberMark Richards15-Oct-15 6:20 
GeneralMy vote of 5 Pin
tbc196526-Aug-15 21:56
membertbc196526-Aug-15 21:56 
GeneralMy vote of 5 Pin
Nish Nishant19-Nov-14 9:14
sitebuilderNish Nishant19-Nov-14 9:14 
QuestionMy vote of 5! Pin
Volynsky Alex30-Oct-14 10:11
professionalVolynsky Alex30-Oct-14 10:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web03 | 2.8.190114.1 | Last Updated 30 Oct 2014
Article Copyright 2014 by Marius Bancila
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid