Maybe C++ hasn’t jumped the shark after all

A couple years ago I wrote a blog post Has C++ jumped the shark? I wondered how many people would care about the new C++ standard by the time it came out. I doubted that it would matter much to me personally.

… if something is hard to do in C++, I just don’t use C++. I don’t see a new version of C++ changing my decisions about what language to use for various tasks.

The new standard is out now, and I find it more interesting that I thought I would have. I’d still rather not use C++ for tasks that are easier in another language I know, but sometimes I don’t have that option.

When I heard about plans to add lambdas and closures to C++ I commented here that “it seems odd to add these features to C++.”  Now I think lambdas (anonymous functions) and closures may be the most important new features for my use.

One of my favorite features in Python is the ability to define functions on the fly. If I’m writing a function and need to create a new function to pass as an argument, then I can define that function on the spot. By contrast, C++ has not let you define a function inside a function. Now you can.

(You still cannot define a named function inside a C++ function. However, you can create anonymous functions inside another function. If you save that anonymous function to a variable, then you’ve effectively created a named function.)

Here are a couple examples of how it can be very convenient to define little functions on the fly. First, optimization software typically provides methods for minimizing functions. If you want to maximize a f(x), you define a new function g(x) = -f(x) and minimize g. Second, root-finding software typically solves equations of the form f(x) = 0. If you want to solve f(x) = c, you create a new function g(x) = f(x) – c and find where g is zero. In both cases, the function g is so trivial that there’s no need to give it a name. And more importantly, you’d rather define this auxiliary function right where you need it than interrupt your context by defining it outside.

Anonymous functions can also have associated data obtained from the context where they’re defined. That’s called a closure because it encloses some context with the function. This is very often necessary in mathematical software development. For example, say you have a function of two variables f(x, y) and you want to integrate f with respect to x, holding y constant. You might then think of f as a function of one variable with one constant, but the compiler sees it as a function of two variables and will not let you pass it to an integration routine expecting a function of one variable. One way to solve this problem is to use function objects. This isn’t difficult, but it requires a lot of ceremony compared to using closures.

For specifics of how to use lambdas and closures in C++, see Ajay Vijayvargiya’s article Explicating the new C++ standard (C++0x), and its implementation in VC10.

More C++ posts

19 thoughts on “Maybe C++ hasn’t jumped the shark after all

  1. “The new standard is out now, and I find it more interesting that I thought I would have. I’d still rather not use C++ for tasks that are easier in another language I know, but sometimes I don’t have that option.”

    Seriously? Took you kinda long time to realize that you have to use the right tool for the right job, don’t you think?

  2. Finding the “right tool for the right job” is subtle. Years ago I might have said something like “It would take me an hour to write this in C++ or ten minutes in Perl. I’ll use Perl because it’s the best tool for the job.” Now I’d say that reasoning is short sighted.

    For one thing, the time it takes to write code isn’t everything. There’s also the time to test it, modify it, move it around, etc. For another, I can’t just consider my own productivity if someone else might need to be responsible for the code in the future. I have to take my colleagues into consideration when I decide what the best tool for a job is.

  3. C++ lambdas work fine when your invocation target is a template function, but I do find it unfortunate that you cannot use them to define a named closure type (such as the delegate in C#).


    // I want to make a big function that accepts a function as a parameter.
    // I would like to minimize code bloat and not have it be a template function.
    void i_take_a_lambda( ClosureType f ) { /* a big function body */ }
    ClosureType f1 = []() { /* etc */ }; // Not possible
    auto f1 = []() { /* etc */ }; // The only way to store a reference to a lambda
    i_take_a_lambda( f1 ); // Not possible unless i_take_a_lambda is a template function

  4. We lived and died by closures in ActionScript 1 and 2. In functional and/or scripting languages where scope is loose, they are uber convenient to define a response handler to some event. Additionally, you can do it right next to where the event is defined so it’s easy to find vs. some class member function you gotta search for. They were a little strange with garbage collection, though. I never really understood when they were eligible since the only person who had a reference to them was the internal activation object (thing that makes your function and puts it on the stack). This got stranger still with Mark & Sweep… now you’re making me want to go find out.

    I don’t know C/C++ (I keep getting suck on Chapter 4: Pointers), but if C’s got closures now, that’ll really help not just in the math stuff you mentioned, but more quickly to develop as well.

  5. it requires a lot of ceremony

    I like this term: having done a lot of C# lately and recently returned to C++, I was casting about for a term to describe the additional friction.

    Next to Objective-C, though, C++ is still my favorite language.

  6. @Ben

    You actually can, if you use:


    typedef std::function ClosureType;
    void i_take_a_lambda( ClosureType f ) { /* a big function body */ }
    ClosureType f1 = []() { /* etc */ }; // Not possible

  7. Anonymous Coward

    You can’t nest functions in C++, but you can define a local struct with a static function. Eg,
    int test() {
    struct priv {
    static int inner() { return 5; }
    };
    return priv::inner();
    };

  8. Thanks for explaining closures. I came across the word a couple of years ago, looked it up in Wikipedia and was completely baffled by the explanation.

  9. Matthias: with lots of care.

    C++ closures allow you to specify whether a variable should be captured by value or reference. If you do the former, then the closure *owns* its own copy of the object, and lifetime issues are moot. If you do the latter, then, as always in C++, it is up to you to ensure that you don’t use the reference after the referenced object has been destroyed.

    So yes, the absence of a garbage collector means you *can* shoot yourself in the foot, but it’s really no different from the rest of C++.

  10. It’s good to see C++ has finally caught up with Borland Pascal :-P nested, named functions do come in handy.

  11. In my world Lambdas are probably the work of the devil – or a least someone with more time on then hands than they should!

    Overall they make code more complicated (as most new features do) and a lot more unreadable! Simpler techniques can do the same, yes maybe a bit more typing but often easier to understand and maintain.

    One of the joys of a good language is that there is only one way to do something, yes rich languages seem clever but they are harder to learn – read easier to abuse.

    Perhaps if compilers were 100% reliable I might be in more favour of new features more – but more often than not they are not perfect (looking at you Microsoft).

    Or is this just being in favour of a ‘RISC’ software philosophy

  12. Professor Fassbender

    You can all celebrate the death of C now. It was a 3GL, an algebraic language delightful to mathematicians and unfriendly to the stupid, the lazy and the ADD, that you all complained about not being easy enough. Well, now its going to be the worst of all 4GLs, like this:

    http://www.sam-hane.com/sass/oshacowb.htm

    Sissies. Aybabtu! Bwahahaha

  13. Questions – Do the new features:

    – accomplish tasks not otherwise available?
    – comply with the programming model implicit in the base language?

    If so, maybe they’re “good things” – if not, maybe they’re no more than attempts to appease programmers from other languages, who find the base language “too hard” to understand due to biases and preconceptions from their other programming experiences. Those are endpoint on the continuum of possibilities, and the real value is likely somewhere on the interior of the interval, but I tire of lazy, inflexible programmers with too little rigor or gumption to learn how to do things in the language as-is.

    [soap_box]
    In addition to programming it on mainframes, starting in ’85, I wrote APL in several dialects for desktop and server-side processing over the course of the last twenty years of my corporate life. During that time, I witnessed multiple additions to the non-mainframe versions of APL that I “lovingly” refer to as the “C-ification” of APL. The additions did not accomplish tasks otherwise unavailable or comply (even remotely) with the base programming model of APL. In all case, the new structures appeared to be attempts to emulate linear, iterated, block-structured models that were easier to understand and employ by programmers who were “comfortable” in some other language, especially C/C++, and didn’t want to expend the effort to learn the native elegance of parallel, array processing and functional programming. The sole virtue of those implementations that included the additional elements was that they did not require use of the new structures – the new elements were merely made available (for weaklings). None of the old, professional APL programmers with whom I was acquainted used the C-ified elements, except as some of those were incorporated into library code distributed with the implementations.
    [/soap_box]

    I hope “your” new features serve useful ends for you without subverting good, solid C/C++ programming by your lesser brethren.

    (some other) Richard

  14. Brian: you’d hate to work on my code. Your comment caused me to review the C# class that’s currently open in VS. Every method has at least one lambda in it. I guess that’s why I miss them so much when I work in C++.

    John: function objects … require a lot of ceremony : Awesome description. I recently worked on a project where I did this and kept thinking “There’s got to be a better way. Wish I was in C#.”

  15. I belive C/C++ will stay with us as long as device drivers and operating system written using it. What I would like to see is to have a new language like ‘D’ that cleaner has the best of all languages as far as features and sytax go, but would translate into C/C++ as the intermiediate language and then compiled the usual way. This would allow using all existing C/C++ code but help transitioning out of C/C++ .

  16. (some other) Richard:

    Questions – Do the new features:

    – accomplish tasks not otherwise available?
    – comply with the programming model implicit in the base language?

    No and Yes. But to get to the heart of your question, this isn’t quite the same situation as you describe with APL. Rather than grafting features and divergent models wholesale from other languages, the C++ standards group has put a lot of time and thought into making sure lambdas would blend seamlessly with the rest of the language. They’ve taken an oft-used idiom (function objects) that was easier to express in several other popular languages, and created a new bit of syntax to express that idiom inline and with less “ceremonial” syntactic adornment.

    One example of the careful attention that was given to integration is the closure mechanism’s support for both capture-by-value and capture-by-reference. C++ is very attentive to the difference between the two, and allows the programmer to decide which they want to use at any given time, so the committee made sure that that flexibility carried through to closures, rather than dictating one choice or the other.

    Similarly, the very natural way that lambdas integrate with STL’s algorithms and predicates not only provide a natural use case for lambdas, but also improve the usability of the STL at the same time. Each improves the other. I hate to use the cliche word “synergy,” but it applies.

    Most of all, this is definitely not an appeasement of those who find C++ “too hard”. This will in no way make the language any easier to grasp. If you didn’t get C++ before, this won’t change that. It merely provides opportunities for those who do fully understand C++ to make their code more succinct and improve its locality.

  17. George,

    I absolutely appreciate your entirely cogent and appropriately coherent reply. If your understanding of my [soap-box] and the related issues as respects the new features of C++ are correct, I can relent my arguments and be happy for C++-ers. ‘Tis a rare and wonderful language evolution that admits new features initially, seemingly alien to a language that nevertheless lean decidedly more to the “yes” end of my question spectrum than the “no” end.

    As a mere tyro in C/C++, I freely yield to your superior grasp of the issue for C++.

    TTFN,
    (some other) Richard

  18. “(You still cannot define a named function inside a C++ function. However, you can create anonymous functions inside another function. If you save that anonymous function to a variable, then you’ve effectively created a named function.)”

    Well, in Pascal nested functions have access to the locals of the function they’re nested in, without the need for closures.

Comments are closed.