Readit News logoReadit News
Animats · 6 years ago
The amusing thing is that C++ still doesn't have nested functions. (Although I think that was a GCC feature at one point.)

As I point out now and then, there are two basic concepts here - closures and anonymous functions. A lambda is both. All four combinations (functions with/without closure, and with/without names) are possible. There's a tendency to confuse the two. An anonymous function is syntactical sugar. A closure is a run-time construct.

- Python and Javascript have nested functions with inheritance, which are closures. Most garbage collected languages have that, because it's easy to implement. Python also has lambdas, which von Rossum didn't want to put in but did to shut up the crowd that wanted them.

- Rust has nested functions without inheritance. A nested function can't access its outer scope. Rust also has lambdas, and they can inherit. C++ has now gone the same route. Non-GC languages need compile-time work to implement a closure, since something has to make a copy of what's inherited and worry about its lifetime.

- It's possible to have anonymous functions without inheritance. The classic example is a sort key function, where a sort function is called with a function that defines the test.

inetknght · 6 years ago
> A lambda is both.

    bool foo() {
        return [](){return false;}();
    }
No capture here; no closure. Adding `-O2` to GCC inlines the lambda.

> The amusing thing is that C++ still doesn't have nested functions.

Also amusingly, it does have anonymous types which can have function members.

    bool bar() {
        struct {
            bool orly() {
                return false;
            }
        } a{};
        return a.orly();
    }
It's fun to send this type of stuff to code review.

Edit: Oh and I forgot; you can refine the member to be static so you don't need to instantiate it first.

    bool bar() {
        struct a {
            static bool orly() {
                return false;
            }
        };
        return a::orly();
    }
[0] https://gcc.godbolt.org/z/Pjdnre

[1] https://gcc.godbolt.org/z/hc73GP

WalterBright · 6 years ago
D has nested functions that can access the enclosing function's variables (and it's enclosing function's variables, on up the call stack!).

It's a feature I first encountered with Pascal.

It's simple to implement. After using them extensively for years now, they're a big improvement.

ridiculous_fish · 6 years ago
Are D's nested functions truly functions - for example are they ABI-compatible with a top-level function? If so, how do they get access to the stack frame?

gcc has this feature, it is implemented by constructing a trampoline-style function on the stack. This requires an executable stack, so isn't really a thing any more.

quantified · 6 years ago
Part of why I miss programming in Pascal, which was only in school. Not enough minutes in the week to get to enjoy D.
ben509 · 6 years ago
Both Pascal and C borrow heavily from ALGOL, which both languages tried to simplify by leaving out various features.
nwallin · 6 years ago
Lambdas are (among other things) nested functions. The "tendency to confuse the two" is a feature, not a bug.

Bob knows how to draw quadrilaterals. He uses this skill to draw rhombuses, rectangles, and parallelograms. IMHO you're arguing that Bob doesn't know how to draw rhombuses because the method he uses to draw rhombuses is a method to draw parallelograms.

Am I misunderstanding you?

ben509 · 6 years ago
I think there is a distinction.

A nested procedure ("function" is a bit misleading in imperative languages) can close over variables in the outer scope, but has to have its own isolated control.

A truly[2] anonymous procedure can also close over control flow:

    fun hasZeros(ints: List<Int>): Boolean {
      ints.forEach {
        if (it == 0) return true // returns from hasZeros
      }
      return false
    }
That's an example from Kotlin's docs. [1]

[1]: https://kotlinlang.org/docs/reference/inline-functions.html

[2]: "truly" in the sense it's never assigned to a name, and is known to not escape its surrounding scope, which is necessary if the compiler is going to allow it to, e.g. do a non-local return.

comex · 6 years ago
In C++, if you leave the capture specifier at the beginning of a lambda expression empty (e.g. [](int x) { return x + 1; }), you can't capture variables from outer scopes, so you effectively have an anonymous function without inheritance. Non-capturing lambdas can be coerced to regular function pointers, so they behave almost identically to true functions, though they technically have different types (before being coerced).

Rust also allows the "non-capturing lambda to function pointer" conversion but (for better or worse) doesn't have explicit syntax for non-capturing lambdas; a lambda automatically qualifies if it doesn't mention any outer variable names.

pjmlp · 6 years ago
Ada, Pascal and C# have nested functions support.

In C#, I never used them, rather reach out for lambdas.

In Ada and Pascal, I never used them beyond book exercises.

Never saw the use beyond cluttering function code.

MiroF · 6 years ago
but not all lambdas are closures
pak9rabid · 6 years ago
I thought the difference is more in how the runtime is structured, i.e. a closure has the ability to make use of the scope in which it's defined in, so that it can make use of variables and functions within it when it's actually called.
ping_pong · 6 years ago
I was a C/C++ programmer from 1995 to 2012, almost 20 years. I moved on to more SaaS-friendly languages, but when I look at C++ now, it's incomprehensible to me. It's an entirely different language. I think I could get back into it, but it's been so long and the gap between where I left it and where it is now is so vast, coupled with the dearth of jobs compared to Python or Go, that it doesn't feel worth it anymore. It's weird knowing that a language that I spend so much time on is relegated to the past.
rumanator · 6 years ago
> I was a C/C++ programmer (...) to 2012 (...) when I look at C++ now, it's incomprehensible to me.

Understandably so. You've used C++ during a time where C++98 reigned and stagnated C++, and left just when that long-standing stagnation finally finished.

Since then 4 new C++ standards have been published. Smart pointers are in. Move semantics are in. Lambdas are in. Auto type deduction is in. Hell, range-based for loops are in.

Your timing was unfortunate. I'm sure programmers who were used to Fortran77 also have problems reading Fortran95 code.

ping_pong · 6 years ago
Yep, exactly this. It was only 8 years ago, but its several generations of changes for C++. As you said, it was very unfortunate timing, but not much I can do except pick up a book and start learning. But I'd probably dedicate that energy to delving deeper into Python, Go or even Java at this point. Sad to see decades of work no longer relevant, but it got me to where I am today, so it was worth something I suppose.
CodeGlitch · 6 years ago
Microsoft have created a pretty good resource for people coming back to C++:

https://docs.microsoft.com/en-us/cpp/cpp/welcome-back-to-cpp...

I have a love/hate relationship with C++, when programming python I really miss the strong typing and other compile time checks, not to mention the performance.

Other times I hate it especially templating compile errors.

boring_twenties · 6 years ago
I've been out of the game for a while, so I could be way off here, but my experience during my brief (15-20 year) career was one could always get paid significantly more for doing C++ than Python.

Which makes intuitive sense to me since it's a lot harder to master C++.

ping_pong · 6 years ago
This isn't true. Go/Python/non-C++ can get you dozens of jobs with very high total comp, well over anything for C++. A Senior Software Engineer at any FANG will get you over 400k/yr TC. Getting an equivalent job with C++ would be much harder.

Deleted Comment

cheez · 6 years ago
Great survey but why on earth does there need to be a book about lambdas in C++???
rightbyte · 6 years ago
I just wish there was nested functions instead of these lambda classes.
xscott · 6 years ago
I sympathize with you here. I kind of like the lambda syntax for simple cases in C++, but the complex cases look painful to me, and based on everything else in C++, I predict there are many special cases to remember.

However, if you just want a more conventional syntax for nested functions, you can almost get what you want by placing a static method inside of a local class, and it's available in older versions of C++ too:

    #include <stdio.h>

    int main() {

        struct foo {
            static void bar() {
                printf("nested\n");
            }
        };

        foo::bar();

        return 0;
    }
If you want it to "close" over your local variables, you'll probably need to actually instantiate an instance of the local class and use a non-static method. Doing this, the capture lists in lambdas almost start to make sense.

cheez · 6 years ago
eh.... having to deal with Python's stupid lambdas, C++ lambdas are far superior. You can always do:

auto func = [](...){....}

for nested funcs

jcelerier · 6 years ago
what would nested functions bring you that lambdas don't ?
tobz1000 · 6 years ago
I'm only mildly familiar with C++, but the support for template arguments in lambdas looks like an analogue for generic closures, which are absent from Rust, but would be really handy at times.
gpderetta · 6 years ago
Note that C++ has had generic closures since C++14, with auto parameters. Template arguments are just a refinement for that.
im3w1l · 6 years ago
So that ForwardToTestFunc right? At what time are the template arguments filled in? Could you call it first with an int and then later with a string, each call forwarding to a different overload?
gumby · 6 years ago
Yes, that’s precisely the use case. The compiler synthesizes two functions, one for each case, then optimizes them separately.
verytrivial · 6 years ago
I've been a C++ programmer for 20 years. Having just seen the example of template arguments in lambda functions:

1) I am reminded of this Larson cartoon: https://imgur.com/gallery/4gR7tX7

2) I have a palpable urge to learn Go.

doorstar · 6 years ago
The more I program in Go, the more retroactively irritated I get at C++. A buddy of mine did a few 'brown bag' sessions on advanced C++ templating and my jaw just dropped further and further at each one. Who has the energy for this? It's hard to write, it's hard to read, it's hard to debug.

For someone who a) knows what they're doing and b) really needs to squeeze every last millisecond out of the code I suppose they could be useful. It made me want to send Rob Pike a thank you card.

jandrese · 6 years ago
IMHO if you're messing with templates in C++ you've probably over-engineered your solution.

There are a few cases where they can make sense, like in the standard library, but most of the time without them your code will be cleaner and you'll never actually run into the case where they pay off.

rumanator · 6 years ago
> A buddy of mine did a few 'brown bag' sessions on advanced C++ templating (...) Who has the energy for this? It's hard to write, it's hard to read, it's hard to debug.

C++ programmers who dedicate themselves to template metaprogramming are like stunt performers. You see them get on their shiny bikes and flashy costumes, you clinch your fists in a mix of fear and awe when they showcase their skills, but in the end it might be entertaining but it has virtually no real practical use beyond extremely niche applications.

jheruty · 6 years ago
C++ templates can indeed get crazy, but I miss them in every other language I use. For example, I recently wrote some code in which I needed to know whether or not a member existed in a class, at compile time. Turns out, there's a way to do that in C++ using templates and overload rules:

https://en.wikibooks.org/wiki/More_C++_Idioms/Member_Detecto...

Yes, conceptually it's a bit nuts, but the fact that you can do stuff like this makes it unlikely that you'll ever get completely stuck trying to implement something.

I personally love C++ because it's just a giant bag of tools, even if one of those tools is a footgun.

jfkebwjsbx · 6 years ago
C++ and Go are different languages with different goals.

Why would a C++ programmer learn Go? They should learn Rust since it is pretty much a cleaned up, modernized C++.

ben509 · 6 years ago
> It made me want to send Rob Pike a thank you card.

Do it, it'll make his day.

guenthert · 6 years ago
Well, features like exceptions and classes don't improve performance. You could write the same code as fast in C, even if it would perhaps be more cumbersome and error prone to read and write. You save perhaps development and maintenance cost, but not runtime cost. Features like lambdas don't improve performance either, they rather make the language easier to write and even more hard to read (think of the name of a function as part of the documentation).

In theory the higher abstraction of C++ provides for optimization opportunities for a sufficiently smart compiler. I have yet to see such though.

Write the little bits which need to be fast in C and the gross in a sane language mere mortals can read (e.g. no 'most vexing parse').

UncleOxidant · 6 years ago
I've programmed in C++ off and on for the last 25 years. I keep wondering if I should spend the time to level up on modern C++ (C++11,14,17,20) or if I might be better off to just chuck it all and dive into Rust and/or Zig and place my bets there instead.
gumby · 6 years ago
I did this a few years ago: picked up C++ 17 after a 25 year hiatus by treating it as a brand new language (and by treating its connection to C as you would Java’s connection to C: a distant ancestral relative).

Modern c++ is a powerful, expressive language. It’s not a “object oriented” language in the fetishistic sense — I don’t write that many classes comparatively. A lot of generic programming and functional algorithms in my case. And I can couple directly to the iron as needed.

Yes there are some unfortunate syntactic issues due to it having evolved from C but that’s how the world works.

secondcoming · 6 years ago
Does anyone actually use Zig in production? I've only ever read about it here on HN.

There's no avoiding Rust. It's becoming the jQuery of systems programming.

C++ has lost the plot in some respects but you don't, and probably won't ever, end up using all of it. A lot of it is to make it easier to write things like the STL, not your typical end-user program. Learn up to 17, there's some good stuff in there. If someone in an interview gives you some highly complex templatey code snippet to analyse just tell them to go fuck themselves.

goodcanadian · 6 years ago
I keep wondering if I should spend the time to level up on modern C++ (C++11,14,17,20)

Yes, absolutely. I (somewhat recently) did that, and I find modern C++ to be almost like a new language. The "modern" features are very useful and powerful.

speedgoose · 6 years ago
Go with Golang if you want simplicity. Go with Rust if you want a complex but still neat programming language. Rust is much better than C++ in my humble opinion. I use these programming language as a hobby and I don't think I will ever use c++ on a new project again.
jfkebwjsbx · 6 years ago
Learning up to C++20 if you are an experienced C++ programmer is easy as long as you only use whatever features you learn or make sense for your project.

Studying everything new is, of course, a much more ambitious goal.

Ericson2314 · 6 years ago
If you learn Rust or C++20, the marginal cost of learning the other should be trivial.

After writing a bunch of Rust, I recently had to learn C++. I've been storing C++ in my brain as "Rust + delta of dubious ideas", which, not to start a flame war, has easily been a better compression algorithm for my brain than storing C++ in isolation.

For example:

- C++, const is part of the type. Clearly wrong.

- Rust, mut is property of variable, or & vs &mut. Correct, but lack of & with mut-polymorphism just causes needless suffering.

- Rust construction: good, Rust destruction: bad

- C++ destruction: bad, basically like Rust's. C++ construction: bad, matches C++ destruction.

(I've been saying for years that drop needs to consume, not borrow the thing to be destroyed, but no uptake :(.)

secondcoming · 6 years ago
I think unambiguous parsing drives the chosen syntax more than pleasing aesthetics.
firethief · 6 years ago
Other languages manage to be parsable without the syntactic aesthic getting Lovecraftian. I think the other factor in C++ is the how much has been added to the language while maintaining backward-compatibility.
xfer · 6 years ago
It's new feature for a standard that's not even out yet. It doesn't matter how long have you been learning C++, unless you are invested in the language design and read proposals.
objektif · 6 years ago
Can we make the beautiful C syntax insufferable? Yes sure just leave it to the committee.
gumby · 6 years ago
Well, these are features that don’t exist in C.

Plus the complex template syntax is mostly unnecessary in C++20

choeger · 6 years ago
One of the worst design decisions in C++ (IMO, of course) is the template syntax. Templates are proper macros, abstractions over code in the sense of lisp. But C++ hides this fact by relying (mostly) on implicit template application and very, very, indirect syntax for template abstraction.

For instance: who came up with the idea of templated lambdas? Yes, manually managed closures are very C++, and yes, templated closures are also very much C++. But now we have templated closures that capture local variables IF THEY EVER HAPPEN TO BE INSTANTIATED. What the actual cluster eff?

valemidas · 6 years ago
Perhaps I'm just dense today, but I'm still not seeing the problem with templated lambdas... if you could spell it out, I'd be much obliged!

Also, I'd assume the closure is instantiated (and the vars captured) regardless of whether its ever called, but I could be wrong.

kazinator · 6 years ago
Can you imagine C++ templates without template parameter deduction?

What's the alternative: spelling out all the parameter values, wherever the template is used?

    mult<int, double>(x, add<double, double>(y, z));
C++ templates use type information to deduce parameters. Lisp macros may be expanded before any type analysis takes place.

The fact that you can just write:

    mult(x, add(y, z))
and the language figures out the parameters for the mult and add templates from the declared types of x, y and z doesn't really have any parallel in mainstream Lisp dialects.

The idiomatic solution would involve making the functions themselves generic so they dispatch on the actual types of the arguments.

Anything relying on declared or inferred type would be a second-class citizen in a dynamic language anyway, where declarations are optional, and inference may be imperfect or absent.

SamReidHughes · 6 years ago
It doesn't capture the variables "if they ever happen to be instantiated." They get captured once; the lambda gets instantiated. It's the operator() method that is templated, not the lambda object's type itself. Basically a template lambda might be equivalent to this:

    struct foo {
        std::string capture;
        template <class T>
        std::string operator()(T x) {
            return capture + std::to_string(x);
        }
    };

gpderetta · 6 years ago
Templated lambdas are extremely powerful. Think for example of (recursive) visitations of etherogeneous containers.
ben509 · 6 years ago
I tried; the house began shaking, there was a sudden eclipse and blood started trickling down the walls. Is that supposed to happen?