Hacker News new | ask | show | jobs
by betterunix 4689 days ago
"at least C++'s complexity offers superbly powerful features and excellent performance, and at least it's understandable how and why this complexity thus arises"

There are certain aspects of C++'s complexity that are inexcusable, especially in C++11. Why force programmers to figure out how to break cyclic references in their reference-counted smart pointers instead of just providing a real (but optional) garbage collected pointer type? Why force programmers to figure out how variables should be captured in a lexical closure instead of just always capturing by value (if you want capture by reference, why not just capture a reference by value?)? Why is there still no reliable way to report errors that occur in destructors? Why is error recovery still so problematic in C++, when other, older languages manage to provide useful facilities?

Most of C++'s problems are the result of the attempt to satisfy everyone's needs simultaneously. Rather than doing one thing well, C++ does many things poorly.

1 comments

> Why force programmers to figure out how variables should be captured in a lexical closure instead of just always capturing by value (if you want capture by reference, why not just capture a reference by value?)?

I agree with most of the points but this one seems suspect to me. This would break code like (forgive the possibly wrong C++11 syntax):

    auto sum = 0;
    std::for_each(my_vector.begin(), my_vector.end(), [](x){ sum += x; })
We actually made the mistake in Rust of making closures capture by reference or by value depending on what type of closure it is, which confuses newcomers immensely. It's scheduled to be fixed by making all closures capture by reference (and if you want to capture by value, use an object instead).
Hair-splitting point: your example is not very good, because for_each is the wrong way to do what you are trying to do. The STL already has the right way to do it:

http://www.cplusplus.com/reference/numeric/accumulate/

It is also worth pointing out that you can always thread state through accumulate/reduce/fold and achieve the effect of capturing references / having mutable objects in iteration constructs like for_each. That is how you see things being done in functional languages, and I find myself doing the same in Lisp with some regularity (even though you are generally dealing with references in Lisp; pure functions are usually more readable and less error-prone, at least in my experience).

In C++ there is another reason that capture-by-reference is a sensible default: there is no garbage collector. It is up to the programmer to ensure that the lexical environment remains valid for the lifetime of the closure, and so you can only capture locals by value if you return a closure. C++ programmers wind up having to capture smart pointer types by value in that situation anyway, which is basically what I said: if you want to capture by reference, create a reference (or pointer or smart pointer or whatever) and capture the value of the reference itself.

> In C++ there is another reason that capture-by-reference is a sensible default: there is no garbage collector. It is up to the programmer to ensure that the lexical environment remains valid for the lifetime of the closure, and so you can only capture locals by value if you return a closure.

That's true, and it's why we applied the same reasoning to Rust. But we ended up with a very confusing situation. The definition of "closure" in an imperative language really implies capturing by reference; virtually all imperative languages that aren't C++ (or Rust) work this way (in Java it's a little muddy because of the final restriction, which is much maligned because it violates this intuition).

A closure that captures by value just isn't a closure by most programmers' definitions, and I think that the language would be better off treating it as something syntactically different from a closure.

This is the working code (you have to declare your intention to capture according to the g++ and clang++ error output, and x requires a type in the parameter list):

    auto sum = 0;
    std::for_each(my_vector.begin(), my_vector.end(),
            [&sum](unsigned x) { sum += x; });
I'm sure it could be golfed further though.
That code is wrong. Its obvious why its wrong when you think about the closure representation. Forcing a developer to understand the implementation is not inconsistent with the C++ mentality (see: everything having to do with inheritance and parameter passing).
> That code is wrong. Its obvious why its wrong when you think about the closure representation.

I don't follow you. There's nothing inconsistent about allowing the closure representation to capture by reference. Indeed C++ allows this, if you write the appropriate capture clause.