C++ Developers Just Got Lambdas?

March 14, 2008

tags:
no comments

Well, no lambdas yet (unless you look at the proposals for the upcoming C++0x standard and be your own judge), but a significant set of additions to the C++ toolset.  I’m talking about the Visual C++ 2008 Libraries Feature Pack Beta 1, commonly referred to as TR1 (licensed from Dinkumware) even though it comes with an incredibly cool MFC update as well, licensed by Microsoft from BCGSoft.

First of all, if you’re a hardcore C++ developer who is interested in what kind of progress the standards committee has been making, you simply have to download this “Feature Pack” and play around with it.  If you’re really hardcore, you might be into reading the actual document (“Technical Report 1”), but I’d pass if I were you.

What kind of goodies does the Feature Pack have in store for us?

  • MFC Update – Office Ribbon and Vista interface with Visual Studio-style docking toolbars and windows, advanced MDI tabs, new GUI controls (such as the paint surface) and many other welcome additions;
  • TR1 Implementation – Smart pointers, function binders, unordered containers, almost every RNG implementation out there, some handy math functions, and additional, smaller pieces of functionality.

Passing functions around?  CurryingFunctors?  We haven’t landed in Scheme-land (or even C# 3.0) yet, but TR1 surely gives us some of the library support for producing very powerful things by binding functions to partial functions and parameters.  Consider the following C# 3.0 segment:

//Convert a two-parameter function to a specific one-parameter:
Func<int, int> CurryAdd(Func<int, int, int> func, int a) {
    return x => func(a, x);
}

Func<int, int, int> Add = (a,b) => a+b;
//Add can take two numbers and add them
Func<int, int> Add5To = CurryAdd(Add, 5);
//Add5To can take a number and add 5 to it
Console.WriteLine(Add5To(3));

It’s surely elegant how we can manipulate lambda expressions and functions all around.  But now in TR1 we can simulate a similar effect for C++:

int Add(int a, int b) { return a+b; }
//Add can take two numbers and add them

function<int(int)> Add5To = bind(Add, 5, _1);
//Add5To can take a number and add 5 to it
cout << Add5To(3) << endl;

bind comes from <functional> and stuff like _1 requires you to have the std::tr1::placeholders namespace in scope.  These placeholders give you the nice ability to do stuff like:

void PrintCoords(int x, int y) {
    cout << "x = " << x << ", y = " << y << endl;
}

function<void(int,int)> printSwappedCoords =
    bind(PrintCoords, _2, _1);    //Reverse the order
printSwappedCoords(5, 3);
//Prints: x = 3, y = 5

Additional classes around the framework (like reference_wrapper) give you the ability to simulate C# outer variables by capturing functor parameters in the functor itself.  Compare C# 3.0 first:

Action<int> f = x => Console.WriteLine(++x);
int i = 15;
Action f2 = () => f(i);    //i is bound now
f2();    //Prints: 16
++i;
f2();    //Prints: 17

And now compare TR1:

void PrintAndIncrement(int x) { cout << ++x << endl; }

int i = 15;
function<void()> f2 = bind(PrintAndIncrement, cref(i));
f2();    //Prints: 16
++i;
f2();    //Prints: 17

Thanks to the new mem_fn support, all of this functor coolness is also available on member functions, with almost no limitations.  Compare C# first:

public static IEnumerable<T> Transform<T>(this IEnumerable<T> coll, Func<T, T> transform)
{
    foreach (T elem in coll)
        yield return transform(elem);
}
public static void ForEach<T>(this IEnumerable<T> coll, Action<T> act)
{
    foreach (T elem in coll)
        act(elem);
}

var initials = new string[]{"Sasha","Masha","Grisha"}.Transform(s => s.Substring(0,1));
initials.ForEach(Console.WriteLine);

Yes, we can do that with TR1, and look at that amazingly beautiful code (transform comes from <algorithm>):

vector<string> names;
names.push_back("Sasha");
names.push_back("Masha");
names.push_back("Grisha");

vector<string> initials;
transform(names.begin(), names.end(), back_inserter(initials),
    bind(&string::substr, _1, 0, 1));

copy(initials.begin(), initials.end(),
    ostream_iterator<string>(cout,"\n"));

Internally, bind uses mem_fn here to give us a functor based on string::substr, which is a member function (with an unmatched number of arguments) unlike most of the things we used in previous examples.  Note that this is the truly elegant solution, while what we did in C# was just work around the problem by using a lambda which takes the three parameter challenge and takes it down to one: s => s.SubString(0,1).

Let’s leave the lambdas alone – there’s so much other coolness lying around.  For example, consider the full-blown regular expression support, including non-greedy qualifiers, capture groups, and more:

regex r("([A-z0-9]+)@([A-z0-9\\.]+)");
for (string s; getline(cin, s); )
{
    smatch matches;
    if (regex_match(s, matches, r))
    {
        cout << "User: " << matches[1]
             << ", domain: " << matches[2] << endl;
    }
}

Cryptographers and mathematicians will surely love to discover the contents of the <random> header file:

image

…and everyone else will rejoice at the availability of unordered containers based on a hash function, similar to .NET’s Dictionary<> and HashSet<>.  BTW, the values aren’t even required to be unique: we have an unordered_set<> and an unordered_multiset<>; an unordered_map<> and an unordered_multimap<>, to suit everyone’s taste.

Finally, some reference counting smart pointers such as shared_ptr<> and weak_ptr<> will free us from scrupulously thinking where objects might get destroyed under our nose.  Reference counting doesn’t replace garbage collection: cycles (just like in COM) will still result in a leak, so if you have object A referencing object B and B referencing A back, these two objects will never be deleted without external interference.  BTW, shared_ptr gives you derived-to-base polymorphic semantics; both ways, with static_pointer_cast and dynamic_pointer_cast.  Finally, shared_ptr works perfectly with containers, so it’s a great wrapper if you need to take a noncopyable or polymorphic type and put it in a vector<>.  If you add weak_ptr to the mix, you’ve got everything you need for C++ resource management:

template <typename T>
class Timer {
private:
    const weak_ptr<T>& _observable;
    int _times, _period;
public:
    Timer(const weak_ptr<T>& o, int times, int period) :
      _observable(o), _times(times), _period(period) { EnableTimer(); }
private:
    void EnableTimer(bool enable=true) //...
    void FireTimer() {
        shared_ptr<T> realPtr = _observable.lock();
        if (realPtr) { realPtr->OnTimer(); }
        else { EnableTimer(false); }
    }
};
struct Timerable {
    void OnTimer() //...
};

//And now somewhere in code:
Timer<Timerable>* p;
{
    shared_ptr<Timerable> t(new Timerable());
    p = new Timer<Timerable>(t, 10, 10);
    //when t runs out of scope, the timer will stop
    //when it tries to invoke the weak_ptr's function
}

The above code gives you a timer which keeps a weak_ptr to its target, and therefore will automatically stop whenever the target object is released.  (This is very much like .NET’s WeakReference, which can be used in a similar way.)

Oh, and one minor thing: TR1 specifies compiler support for type traits, the ability to ask questions about facilities provided by a type during compile time.  For example:

cout << std::tr1::has_trivial_copy<MyType>::value;
cout << std::tr1::is_base_of<Base, Derived>::value;
cout << std::tr1::is_convertible<int, float>::value;
//...and many more

By the way, these facilities have been available in Microsoft’s compiler for quite some time, as intrinsics of the form __has_XXX or _is_XXX (see Compiler Support for Type Traits).

I’ve surely focused on TR1 in this post, but some day Alon or I will surely drill into the depths of the new MFC update.  Still, to whet your appetite and encourage you into the download (which comes with lots and lots of cool samples!), here are some screenshots of what you can get out of the box without any customization, custom controls, coding work etc.:

Out of the box wizard-generated dialog, using the Office Blue theme image
Same thing, with the Office Black theme image
CMFCImageEditorDialog editing an image (the entire dialog is out of the box) image
Check Mnemonics, Property window showing properties, events and messages (including registration) – MFC feels more like WinForms? image

Minor installation quirks:

  • It says so on the MSDN Download Center page, but I’ll repeat it here: during installation you need to have the original Visual Studio 2008 installation media handy.  If you installed from a network share/drive, that same share/drive must be mapped.  If you installed from a DVD, the DVD must be in the drive.  And so on.
  • The Feature Pack Beta 1 is currently only supported for Visual Studio 2008 Professional and Visual Studio 2008 Team Edition, in the English version only.
Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*