Syntax changes from C++11 to C++20
bfilipek.com
bfilipek.com
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.
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?
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.
Can you provide a ... slightly more real world example of what non-local returns are for? As described it sounds equally capable of shooting both varmints and one's own feet.
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.
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/PjdnreIt'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.
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.
Tl;dr: it's a bit complicated, especially wrt communication with c libraries, but in general they're interchangeable with other functions.
Yes. They're even inlinable!
> for example are they ABI-compatible with a top-level function?
If they are annotated with `static`, yes. If they are not so annotated, they are ABI-compatible with non-static member functions. In fact, in D, a pointer to a non-static member is interchangeable with a pointer to a non-static nested function. This makes for much convenience when dealing with algorithms that accept such pointers (called "delegates" in D):
> how do they get access to the stack frame?
And extra argument is passed to the function which is the frame pointer to the calling function. (This is called a "static link". The "dynamic link" is the return address.) This can be thought of as the "this" pointer to the "struct" that represents the caller's stack frame, hence the binary compatibility with member functions.
To go back to the enclosing enclosing enclosing function's stack frame, you dereference the static link 3 times. The tricky part is optimizing these dereferences away when there are no captured variables in a stack frame.
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.
Which makes intuitive sense to me since it's a lot harder to master C++.
I've never worked at one myself, but they do hit me up from time to time, and seem to be mostly interested in my C++ experience.
As I said in my disclaimer, my actual experience is very dated at this point, but a strong C++ developer could earn 400k+ even way back in 2011 in the NYC financial industry. Back then, I don't think I'd heard of any Python developers making much more than 150k.
Nowadays, the people I know working in C++ in NYC finance are all making significantly more than 400k.
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.
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.
auto func = [](...){....}
for nested funcs
typedef void fp_t();
void foo(fp_t fp)
{
fp();
}
int square(int num) {
void square_nested()
{
num = num * num;
}
foo(square_nested);
return num;
}
That would work as long as you don't need copy capture. int square(int num) {
auto nested = [&num](){
num *= num;
};
nested(num);
return num;
} auto nested = [](int& num){
(or switch calling nested to have no params)https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html#Nes...
std::sort(vec.begin(), vec.end(),
[] (const auto& lhs, const auto& rhs) {
return lhs.whatever < rhs.whatever;
});
or auto sort_by_whatever = [] (const auto& lhs, const auto& rhs) {
return lhs.whatever < rhs.whatever;
};
std::sort(vec.begin(), vec.end(), sort_by_whatever); const auto iAmANestedFun = [stuff](stuff) -> stuff { ... }
Particularly, if we could assume that nested functions automatically and properly capture the arguments to the parent function. const auto nestedfunc = [=](a,b) { /* */ };
Which isn't that much different to me than { /* some scope */
function nestedfunc(a,b) { /* */ }
}
const and auto are needed but because it's C++. Beyond that, you have an equals sign which is needed here but [=] which is three characters. Again, small differences, not sure why it looks that much worse. auto triple = [](auto x){ return 3 * x; };well, how would you do that ?
IIRC Rust uses capture modifiers similar to C++.
PHP closures have to specify which variables they close over, and whether it's done by-val or by-ref [0].
Nowadays there's also a shorthand single-expression-closure syntax that closes implicitly and by value [1].
[0]: https://www.php.net/manual/en/functions.anonymous.php#exampl...
auto f = [&](...) { ... };
That covers the majority of cases.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.The only feature of lambdas that a proper nested function would miss is the "copy capture".
Yeah, reference by default would fit with what people probably expect from other languages, but without a garbage collector (or similar) that's only valid for downward passed functions. Copying allows your nested function to escape the lifetime of the calling function, which might require some other/additional syntax.
1) I am reminded of this Larson cartoon: https://imgur.com/gallery/4gR7tX7
2) I have a palpable urge to learn Go.
I don’t mean this as an insult: Pike is quite open about choosing a different region of the design space; he had good reasons for it, they just happen not to work for me.
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 :(.)
This correctly prevents one from calling Drop::drop twice without extra hacks, and demonstrates that the location is completely deinitialized and can be reinitialized to anything.
It is also dual to how, seemingly magically,
let x;
x = foo();
works without Rust thinking there is an old value of `x` to get rid of.Wouldn't calling a destructor on an object twice will also compile in C++? I don't really see this as an issue in either language; the solution is "don't ever call `drop`/destructors manually"
fn main() {
let mut v = vec![1, 2, 3];
v.drop();
}
building: Compiling playground v0.0.1 (/playground)
error[E0040]: explicit use of destructor method
--> src/main.rs:4:7
|
4 | v.drop();
| ^^^^
| |
| explicit destructor calls not allowed
| help: consider using `drop` function: `drop(v)`
error: aborting due to previous error
std::mem::drop, which it refers to, does take by owner.Your parent's point is that this is kind of a weird corner case that only applies to drop.
It’s pretty hard to express that an object must be invariant without a thing like that. You can’t do so with the storage itself as the object may be passed to a function.
Although there are legit cases for what you describe, it’s a pretty uncommon case.
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.
Likely not, or at least they shouldn't, as it still has yet to have a 1.0 release and is undergoing constant changes in syntax and API.
However, that doesn't mean one can't take the time to learn it right now. Zig shows a lot of promise for being a real contender in this space once it does have a stable release.
As I'm mostly retired at this point, I don't really care how marketable skills are anymore. I go for what looks interesting.
I'm translating a subset of statically typed Python to C++:
http://www.oilshell.org/blog/2020/05/translation-progress.ht...
http://www.oilshell.org/blog/2020/01/parser-benchmarks.html
I use very much a "pidgin" subset of the language -- one that is easy to step through in the debugger! The problem with all these fancy new features is that you're more likely to trip over bugs in the debugger, e.g. incorrect line number information and stack traces.
So the generated code doesn't even use operator overloading, which e.g. C++ 11 range for loops require. I want to be able to see everything. Yet I also want to use features like templates, virtual methods, and strict enums, which C doesn't have.
-----
Another "modern" project that does this is the Souffle datalog compiler, which generates "monolithic" C++ programs with heavy use of C++ templates:
https://souffle-lang.github.io/translate
(I heard about it due to its role in prototyping Rust's type system)
C++ templates are not fun to write but they have some interesting properties for code generation.
-----
So I would happy if we stopped writing C++ by hand. But I do not like the alternative rewriting inferior versions of existing programs/libraries in entirely new languages. They often have new bugs, are slower, and have fewer features.
I really like what Zig is doing to reuse existing code (i.e. bundling a C compiler). I think that both Rust and Go ecosystems have led to a lot of fragmentation and redundant efforts.
The "History of Clojure" paper has a nice view on that. Why Clojure is tied to the JVM. So it's part of the ecosystem rather than fragmenting it.
Zig is cool but still very much unstable, running it in production is very much not recommended and/or insane. There's still quite a lot of compiler/stdlib bugs, design edge cases, and breaking changes including introduction of new bugs.
But it's amazingly refreshing for hobby stuff!
I'm not sure if that's meant as a good thing or a bad thing. Given the target audience, I expect a good thing, as for people not entirely immersed into JavaScript and web development at the time (which I expect most people reading about C++ features here were not), jQuery was a godsend. It gets kind of a bad wrap these days, but from my perspective, JavaScript just adopted all the paradigms it encouraged into the language so it's no longer needed. Hard to argue with that success, IMO.
A more concrete example of this is Windows and Java's use of UCS-2 as their character type. That was a forward thinking move which then became a painful legacy issue when Unicode changed.
That's inevitably going to happen to Rust to some extent, but it's targetting a realm that is far slower to evolve and it seems like they are very mindful of history and theory.
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.
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.
Studying everything new is, of course, a much more ambitious goal.
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.
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.
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').
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.
So I am going out on a limb here and assuming that your needing to know whether or not a member exists in a class at compile time is simply something you would never need in a different language because the language would allow you to design for something simpler that satisfies the same requirements. As someone who writes both C++ and C# daily, I often end up comparing the two and most of the time I end up thinking "it's cool that you can do that in C++ but this would have been half the lines and cleaner code in C#".
I didn't want to use inheritance, as tracking is disabled in a shipped build, and I want it to have as little effect on the codebase as possible. So, the simplest option seemed to be to check whether or not the member exists (and assume the name is unique enough an untracked object will not contain it). This was good enough for my purposes.
I'm not as familiar with C#, but attributes seems like a potential alternative, though I'm not sure they can be fully removed from a release build. And, of course, C# is garbage collected, which is not ideal for all types of software. If there is a better alternative, I'd be interested in hearing it though!
Don't get me wrong, I'd absolutely love a "better" C++. Rust is a great language, but I personally don't feel that the safety guarantees are worth it for every class of software. If a video game crashes, the world isn't going to end.
Why would a C++ programmer learn Go? They should learn Rust since it is pretty much a cleaned up, modernized C++.
Perhaps to find a new reason to hate a programming language thanks to Go's GOPATH.
And the most important thing - I can easily read and understand the code written by others.
Do it, it'll make his day.
I sometimes feel like sending one to Bjarne Stroustrup too, because C++ is still an incredible language. I see Rob and Ken talking about how they hate C++ and I worry about poor Bjarne's feelings.
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.
Plus the complex template syntax is mostly unnecessary in C++20
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?
Also, I'd assume the closure is instantiated (and the vars captured) regardless of whether its ever called, but I could be wrong.
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.
struct foo {
std::string capture;
template <class T>
std::string operator()(T x) {
return capture + std::to_string(x);
}
};