Herb Sutter is a very concise and frank speaker, truly a gifted presenter and teacher. Disregarding whether we agree on his approach, how he presents his perspective makes a debate simple and automatic and that’s the true gift he possesses. No snark, no irony, all substance.
Love this! We need a lot more simplification in C++. Spec is getting insane! 25+ years of C++ but I also do a lot of other languages and it's always easier....and as others echo it's complicated for no apparent reason - not for performance. I'm not leaving C++ but c'mon. This in/out/etc feature would also reduce the amount of annoying constructors, assignment operators and other boileplate code just to write a single class too.
While it's a huge improvement over the current conceptual mess of a simple basic task of passing parameters, it's hard not to note that other languages somehow manage to get away with just in, out, and inout parameters...users of those languages can't think of the "move" and "forward" in their worst dreams.
The last slide on efficient abstraction was the best one of the talk. It perfectly encapsulates the best way to proceed: Figure out how to do things by hsnd, gain valuable experience in what works well, what doesn't. Then automate.
Before: "There are 10 methods to achieve something, this is bad. We need one method to replace them all.." After: " "There are 11 methods to achieve something, this is bad..." Jokes aside. Thanks for the talk. Very interesting.
That's the best proposal about syntax changes I've seen before. SFINAE + PF (whatever via "requires" or using old syntax) is just pain to make dispatching of methods to be invoked depending on passed type.
Please God Herb, can you save C++ from the swamp of complexity it is in and which grows with every new standard release. Even the creator of C++, Bjarne, says it is ten times more complex than it need be. As you say. Can we have a compiler switch that errors out on the 90% of things we should not be doing? I know, there is a lot of junk in the language/library that is still there for backwards compatibility and is no longer the recommended thing to do. But if I want to write new code, and learn the "new" C++ the compiler should point out when I'm using those old deprecated ways.
Yeah, I would also love to have a "--modern-cpp" switch and anything deemed old and bad to become a compiler error. I guess some sanitizers and warnings are the best we can get right now.
Never mind the specific proposal here, I think there is a lot of merit in the idea of evolving C++ to the point that we can subset it to "C++ pure", particularly if the conversion of existing code to the subset can be semi-automated. This approach would mean keeping lots of existing tooling and knowledge, instead of throwing everything away and starting from scratch. In my world, that is much more likely to happen that starting everything again in Rust.
I wonder if one could even get rid of "move" and "forward" and let the compiler figure out whether "in" can be promoted to them. If needed, allow the compiler to generate multiple versions of the function for that purpose, i.e., allow every function to be promoted to template form.
This seems necessary in order to solve the combinatorial explosion of function overloads, I just hope that it is not put into the language in a form where there is still a need to use the traditional & and &&. By the way it's also an excellent idea.
For "out", how do you keep track of if it was initialized or not? Surely after X x = uninitialized some paths may initialise it and other's might not? You could do it with some runtime information but that'd be expensive. Maybe you could just use whether the compiler thinks it is maybe uninitialized by that point
You can look up c# rules for that, it's actually easy to compute that and the proposal here is in line with that. It's all statically computed by turning functions into instruction blocks with jumps and analysing reachability. Of course you may still get false positives like this one: int x /* = uinitialized */ if (some complex condition that is always true) x = 123; //
Using just IN and OUT and letting compiler optimize the rest seems to be assuming that there is no aliasing. What if you have f(in T x1, out T x2) and parts of x1 and x2 are aliasing the same object? The first/last use becomes complicated, especially if you have complex execution branches in the function. How are you expecting compiler to deal with it? Or are you going to declare aliasing an UB and let the programmers hang themselves?
You're talking about this like it's not already an existing issue... If you pass std::memcpy two pointers to the same buffer, it's undefined behavior. Actually, this proposal is even better than the status quo on that point, because it actually gives compilers a chance to check for that sort of undefined behavior at compile time, because the uses of those passed arguments is made explicit.
When passing `in` parameters of "heavy" types by const reference you still have problems about lifetime and aliasing. In my experience this unfortunately is not always well understood IMO but a `const&` doesn't tell that the object will not change, but just that you cannot change it *using that reference* (const is a property of the reference, not of the referenced object). The object may change and may even get out of existence in the middle of the body of the function. See stackoverflow.com/a/4705871/320726 for an example where just using `const&` as a "smart way" to pass what should have been a value can lead to bad subtle bugs (despite the code shown I think doesn't break an "good coding" rules).
I would guess that the "move" parameter passing would allow that. The key is that the callee becomes the owner of the value. I assume you'd be able to make copies of it just as in your example. However at the end the value would either be destroyed or moved into yet another call.
That is a good example of what "move" parameters are for. The following would have the identical semantics in the current proposal, where the definite last use is treated as an rvalue: f(move String s) { x(s); // s is treated as an lvalue y(s); // s is treated as an rvalue -- definite late use } This should be identical to the f you wrote, including that at all sites both versions accept only rvalue arguments.
@@herbsutter4340 what if you have a more complex branching in the code f(string&& s) { if(a) x(move(s)); if(b) y(move(s)); } you write it because you know that a && b == false, but compiler can't know it, or it's too hard to deduce it from the rest of the code. For compiler determining first/last use can be nontrivial or impossible.
Great stuff - two thoughts...(1) Surely an `inout` parameter can be optionally modified as opposed to modification being mandatory, and (2) watching the initialisation stuff, we also need to be able to prevent optimisers removing buffer-clearing in security-conscious code.
In slide 31 24:37 you suggest an 'out' argument. Isn't the return value designed exactly for this purpose? Or in what way is an out argument superior to a return value?
Return values are great for values. For more complex objects, you can depend on RVO, but nothing in the function signature can guarantee that today, it's only guaranteed depending on how the implementation is written since C++17. The out argument can also be used to initialize an object through multiple calls. You will still use return values for most cases, but in some cases programmers might prefer out arguments.
Many use return values to indicate "success" as 0, starting by the main function itself. Return values for more than that create more problems than solve.
Can you really introduce "in", "out" and "inout" as keywords without breaking existing code? Maybe it would work - barring collision with non-standard extensions - because of the context where it is suggested...
I thought they would be contextual keywords so they wouldnt trip up any variable names in a function definition. The compiler would sweep the code looking for function parameters an would accept the tokens in, out, etc there
Long ago I suggested creating a namespace mechanism for keywords, otherwise we are stuck with inventing ugly workarounds because some code already used the words in some incompatible way. Even making names context dependent isn't a good solution because imagine how confusing the error messages could be. With keyword namespaces everything could be simplified, no user code can match f(std::in std::string s). Unfortunately my suggestion never got traction.
Would be great if we could have something similar for local variables as well ( also by introducing a new syntax to keep backward compatible maybe): x : int; /*uninitialized;*/ x := int(); /*default initialized*/ x := 2; /* const auto x = 2;*/ x : mutable int = 2 ; /* int x =2;*/ x := initially 2; /* alternative for mutable */ y :=? x; /*variant, pointer or optional like. I.e: the validity of x must be questioned before reading as a compile time check */ y : x; /* const auto& y=x, actually this way it is easy to differentiate changing the value or changing the pointer .. we can have a language reference_wrapper */ Examples for auto “auto” with clear intent: x := foo(); /*copy*/ y : foo(); /*const auto&& x = foo(); */ k :=? foo(); /* could be nullptr .. */ Generally, types shall be deduced, unless required to be defined/ converted .. x : std::tuple = {2,”hello”,y} ; y : std::array = {3,4,5}; z: MyStruct = myFuncThatReturnsTwoOrMoreValues(); This shall be possible for declarations and assignments a z ; /*y references z, initially has referenced x*/ y=5; /* z now equals 5 */ /* u and v are of type optional*/ u = 2; /*set the integer to 2, has_value =true*/ u =? v; /* copy number and has_value flag*/ u=v; /*compile time error, v maybe invalid, use “=?” instead*/ u=v.value(); /*compile time error, v was not checked for validity before reading out*/
The unnecessary complexity and no way to get around it is why I avoided C++ like a plague. When I looked at the parameter guideline, my immediate gut reaction was "Soooo.... why is this not automated by default, with an option to override the default behavior?" I was unsure whether there's some deep complex reason behind it, that my amateur brain simply fails to comprehend; or whether it's a bloat of incremental backwards-compatible patches of legislation from 30+ years of committee decisions.
It's common (even in many standard functions) to reuse the IN function parameter modifying it in the body of the function. It becomes impossible in new notation, neither IN, INOUT would compile. auto f(auto i) { decltype(i) sum{}; for(; i; --i) sum += i; return sum; } f(123); And b.t.w., what's the decltype(i) going to be if it's "auto f(in auto i)"? Now template parameters (and auto) never deduce as reference types, are they going to be deduced as reference types for IN, INOUT, OUT?
In short what you explained is c++ standard miserably failed ... it started with helping adding features to enable devs to write safe code but made them completely confused because of added complexity/ jargon/ cryptic syntax that you are not trying to fix the mess by adding even more complexity/ jargon/ cryptic code, kudos!
in / out / inout is the same as Structured Text. It's fine, and makes it easier to do graphical programming (e.g. CFC). Trying to make C++ an IEC 61131 language? :)
This should have been there even decades ago in good old C, where some guys still struggle with pointers for the very same reason (or at least with the ugly dereferencing operators). And yes, C++ has become way too complicated. Given the fact that in industrial areas, it will take even longer until new standards are finally considered stable enough, and personnel trained enough, to use this in critical machine code. Not to speak the adaption and validation of syntax checking tools. That takes even many more years _after_ the standard is out. And think about that the hurdle for newbies has become higher and higher until they have caught up and can eventually start working productive. No surprise that C++03 is still standard in many places, and C++11 is the "hot shit" in some more advanced teams. Yes, some features of 11, 14, 17, 20 are really nice, and I would like to use them in reality. But, sigh, when I do, it gets "academic" very quickly, and I would write "read only by me" code. It's just too complex.
No 'out' parameter. This introduces the idea of 'uninitialized objects' which is terrible. So, absolutely no 'out' parameter. Besides, we have a perfectly good 'out' parameter, and it's called a function return value. And with things like tuples and structured bindings for reasonably simple multiple return values. So, absolutely no 'out' parameter involving any concept of uninitialized objects. That concept does not currently exist in the C++ language, and it shouldn't be brought into existence to satisfy this proposal. Otherwise, I think this is a good proposal. And, I guess, if you do not introduce the idea of an uninitialized object, an 'out' parameter is OK. The idea of a parameter the function must not read before it writes is useful, and it lends some orthogonality. I still don't like it though.
Herb Sutter is a very concise and frank speaker, truly a gifted presenter and teacher. Disregarding whether we agree on his approach, how he presents his perspective makes a debate simple and automatic and that’s the true gift he possesses. No snark, no irony, all substance.
He always looks well prepared. I wonder if that is just lucky to have that trait or he spends a lot of time preparing his talks.
This would make me like C++ so much more. So simple, yet so effective. This HAS to happen.
You could use Ada and get many other benefits.
It's just a pure beauty of design. When I get something close to it in mainstream, I will have tears came to me.
Herb's proposals giving C++ a way to stay vibrant in the 21st century
This feature would be HUGE for C++.
Lots of great talks in CppCon20 this year. But this is the best in my opinion. Can we rush this feature to C++23 🙏
Not gonna happen unfortunately. There are many things to improve in C++ so this will just have to wait for it's turn. Hopefully C++26/29 :D
Love this! We need a lot more simplification in C++. Spec is getting insane! 25+ years of C++ but I also do a lot of other languages and it's always easier....and as others echo it's complicated for no apparent reason - not for performance. I'm not leaving C++ but c'mon. This in/out/etc feature would also reduce the amount of annoying constructors, assignment operators and other boileplate code just to write a single class too.
This is great. I feel like by the time I'm retired C++ is going to be a really nice language.
Love this proposal and the empirical method used to study accidental complexity in c++. Thank you Herb.
While it's a huge improvement over the current conceptual mess of a simple basic task of passing parameters, it's hard not to note that other languages somehow manage to get away with just in, out, and inout parameters...users of those languages can't think of the "move" and "forward" in their worst dreams.
The last slide on efficient abstraction was the best one of the talk. It perfectly encapsulates the best way to proceed: Figure out how to do things by hsnd, gain valuable experience in what works well, what doesn't. Then automate.
This is really great, nice to know C++ is moving forward to keep it simpler and powerful!!
Great thanks!
Glad you like it!
wow this is really awesome. i hope this all gets implemented
This talk makes so much more sense with cpp2 in mind :)
Before: "There are 10 methods to achieve something, this is bad. We need one method to replace them all.."
After: " "There are 11 methods to achieve something, this is bad..."
Jokes aside. Thanks for the talk. Very interesting.
True, what actually needs to happen is remove those 10 ways which we don't do because... BACKWARDS COMPATIBILITY
This would be so pleasant! One request: for explicit uninitialized please use 'uninit' as the keyword (I feel I may be typing it a lot).
That's the best proposal about syntax changes I've seen before.
SFINAE + PF (whatever via "requires" or using old syntax) is just pain to make dispatching of methods to be invoked depending on passed type.
I like this idea. I hope it can be picked up and added to C++ 23.
Please God Herb, can you save C++ from the swamp of complexity it is in and which grows with every new standard release.
Even the creator of C++, Bjarne, says it is ten times more complex than it need be. As you say.
Can we have a compiler switch that errors out on the 90% of things we should not be doing?
I know, there is a lot of junk in the language/library that is still there for backwards compatibility and is no longer the recommended thing to do.
But if I want to write new code, and learn the "new" C++ the compiler should point out when I'm using those old deprecated ways.
Yeah, I would also love to have a "--modern-cpp" switch and anything deemed old and bad to become a compiler error. I guess some sanitizers and warnings are the best we can get right now.
Wow can't wait to use this, really useful proposal, and well designed as always Herb ;)
I really hope I get to use this in my code soon. This is awesome.
Never mind the specific proposal here, I think there is a lot of merit in the idea of evolving C++ to the point that we can subset it to "C++ pure", particularly if the conversion of existing code to the subset can be semi-automated. This approach would mean keeping lots of existing tooling and knowledge, instead of throwing everything away and starting from scratch. In my world, that is much more likely to happen that starting everything again in Rust.
I wonder if one could even get rid of "move" and "forward" and let the compiler figure out whether "in" can be promoted to them. If needed, allow the compiler to generate multiple versions of the function for that purpose, i.e., allow every function to be promoted to template form.
The more C++ things like this I see, the more I remember why I tend to write in C.
This seems necessary in order to solve the combinatorial explosion of function overloads, I just hope that it is not put into the language in a form where there is still a need to use the traditional & and &&. By the way it's also an excellent idea.
For "out", how do you keep track of if it was initialized or not? Surely after X x = uninitialized some paths may initialise it and other's might not?
You could do it with some runtime information but that'd be expensive. Maybe you could just use whether the compiler thinks it is maybe uninitialized by that point
You can look up c# rules for that, it's actually easy to compute that and the proposal here is in line with that. It's all statically computed by turning functions into instruction blocks with jumps and analysing reachability. Of course you may still get false positives like this one:
int x /* = uinitialized */
if (some complex condition that is always true)
x = 123;
//
Using just IN and OUT and letting compiler optimize the rest seems to be assuming that there is no aliasing. What if you have f(in T x1, out T x2) and parts of x1 and x2 are aliasing the same object? The first/last use becomes complicated, especially if you have complex execution branches in the function. How are you expecting compiler to deal with it? Or are you going to declare aliasing an UB and let the programmers hang themselves?
You're talking about this like it's not already an existing issue... If you pass std::memcpy two pointers to the same buffer, it's undefined behavior. Actually, this proposal is even better than the status quo on that point, because it actually gives compilers a chance to check for that sort of undefined behavior at compile time, because the uses of those passed arguments is made explicit.
When passing `in` parameters of "heavy" types by const reference you still have problems about lifetime and aliasing. In my experience this unfortunately is not always well understood IMO but a `const&` doesn't tell that the object will not change, but just that you cannot change it *using that reference* (const is a property of the reference, not of the referenced object). The object may change and may even get out of existence in the middle of the body of the function. See stackoverflow.com/a/4705871/320726 for an example where just using `const&` as a "smart way" to pass what should have been a value can lead to bad subtle bugs (despite the code shown I think doesn't break an "good coding" rules).
How it'll be possible to write such sample with "in" parameters?
f(String&& s)
{
x(s); // copy
y(std::move(s)) // move in second call
}
I would guess that the "move" parameter passing would allow that. The key is that the callee becomes the owner of the value. I assume you'd be able to make copies of it just as in your example. However at the end the value would either be destroyed or moved into yet another call.
I feel f(forward String s) could do it?
That is a good example of what "move" parameters are for. The following would have the identical semantics in the current proposal, where the definite last use is treated as an rvalue:
f(move String s)
{
x(s); // s is treated as an lvalue
y(s); // s is treated as an rvalue -- definite late use
}
This should be identical to the f you wrote, including that at all sites both versions accept only rvalue arguments.
@@herbsutter4340 what if you have a more complex branching in the code
f(string&& s)
{
if(a) x(move(s));
if(b) y(move(s));
}
you write it because you know that a && b == false, but compiler can't know it, or it's too hard to deduce it from the rest of the code. For compiler determining first/last use can be nontrivial or impossible.
Great stuff - two thoughts...(1) Surely an `inout` parameter can be optionally modified as opposed to modification being mandatory, and (2) watching the initialisation stuff, we also need to be able to prevent optimisers removing buffer-clearing in security-conscious code.
In slide 31 24:37 you suggest an 'out' argument. Isn't the return value designed exactly for this purpose? Or in what way is an out argument superior to a return value?
Return values are great for values. For more complex objects, you can depend on RVO, but nothing in the function signature can guarantee that today, it's only guaranteed depending on how the implementation is written since C++17. The out argument can also be used to initialize an object through multiple calls. You will still use return values for most cases, but in some cases programmers might prefer out arguments.
Many use return values to indicate "success" as 0, starting by the main function itself. Return values for more than that create more problems than solve.
Can you really introduce "in", "out" and "inout" as keywords without breaking existing code? Maybe it would work - barring collision with non-standard extensions - because of the context where it is suggested...
I thought they would be contextual keywords so they wouldnt trip up any variable names in a function definition. The compiler would sweep the code looking for function parameters an would accept the tokens in, out, etc there
Long ago I suggested creating a namespace mechanism for keywords, otherwise we are stuck with inventing ugly workarounds because some code already used the words in some incompatible way. Even making names context dependent isn't a good solution because imagine how confusing the error messages could be.
With keyword namespaces everything could be simplified, no user code can match f(std::in std::string s).
Unfortunately my suggestion never got traction.
Is this going to be able to pass std::unique_ptr in a register (like a raw pointer) or is it an orthogonal problem?
Would be great if we could have something similar for local variables as well ( also by introducing a new syntax to keep backward compatible maybe):
x : int; /*uninitialized;*/
x := int(); /*default initialized*/
x := 2; /* const auto x = 2;*/
x : mutable int = 2 ; /* int x =2;*/
x := initially 2; /* alternative for mutable */
y :=? x; /*variant, pointer or optional like. I.e: the validity of x must be questioned before reading as a compile time check */
y : x; /* const auto& y=x, actually this way it is easy to differentiate changing the value or changing the pointer .. we can have a language reference_wrapper */
Examples for auto “auto” with clear intent:
x := foo(); /*copy*/
y : foo(); /*const auto&& x = foo(); */
k :=? foo(); /* could be nullptr .. */
Generally, types shall be deduced, unless required to be defined/ converted ..
x : std::tuple = {2,”hello”,y} ;
y : std::array = {3,4,5}; z: MyStruct = myFuncThatReturnsTwoOrMoreValues();
This shall be possible for declarations and assignments
a z ; /*y references z, initially has referenced x*/
y=5; /* z now equals 5 */
/* u and v are of type optional*/
u = 2; /*set the integer to 2, has_value =true*/
u =? v; /* copy number and has_value flag*/
u=v; /*compile time error, v maybe invalid, use “=?” instead*/
u=v.value(); /*compile time error, v was not checked for validity before reading out*/
pass by value - it could be attribute [[pass_by_value]]
Great talk!
The new C++ should cross-compile to the backwards compatible complex C++.
The unnecessary complexity and no way to get around it is why I avoided C++ like a plague. When I looked at the parameter guideline, my immediate gut reaction was "Soooo.... why is this not automated by default, with an option to override the default behavior?" I was unsure whether there's some deep complex reason behind it, that my amateur brain simply fails to comprehend; or whether it's a bloat of incremental backwards-compatible patches of legislation from 30+ years of committee decisions.
interesting sounds good
Excellentz
It's common (even in many standard functions) to reuse the IN function parameter modifying it in the body of the function. It becomes impossible in new notation, neither IN, INOUT would compile.
auto f(auto i) { decltype(i) sum{}; for(; i; --i) sum += i; return sum; }
f(123);
And b.t.w., what's the decltype(i) going to be if it's "auto f(in auto i)"? Now template parameters (and auto) never deduce as reference types, are they going to be deduced as reference types for IN, INOUT, OUT?
In short what you explained is c++ standard miserably failed ... it started with helping adding features to enable devs to write safe code but made them completely confused because of added complexity/ jargon/ cryptic syntax that you are not trying to fix the mess by adding even more complexity/ jargon/ cryptic code, kudos!
in / out / inout is the same as Structured Text. It's fine, and makes it easier to do graphical programming (e.g. CFC). Trying to make C++ an IEC 61131 language? :)
This should have been there even decades ago in good old C, where some guys still struggle with pointers for the very same reason (or at least with the ugly dereferencing operators).
And yes, C++ has become way too complicated. Given the fact that in industrial areas, it will take even longer until new standards are finally considered stable enough, and personnel trained enough, to use this in critical machine code. Not to speak the adaption and validation of syntax checking tools. That takes even many more years _after_ the standard is out.
And think about that the hurdle for newbies has become higher and higher until they have caught up and can eventually start working productive. No surprise that C++03 is still standard in many places, and C++11 is the "hot shit" in some more advanced teams.
Yes, some features of 11, 14, 17, 20 are really nice, and I would like to use them in reality. But, sigh, when I do, it gets "academic" very quickly, and I would write "read only by me" code. It's just too complex.
nice
Great!
No 'out' parameter. This introduces the idea of 'uninitialized objects' which is terrible. So, absolutely no 'out' parameter. Besides, we have a perfectly good 'out' parameter, and it's called a function return value. And with things like tuples and structured bindings for reasonably simple multiple return values.
So, absolutely no 'out' parameter involving any concept of uninitialized objects. That concept does not currently exist in the C++ language, and it shouldn't be brought into existence to satisfy this proposal.
Otherwise, I think this is a good proposal. And, I guess, if you do not introduce the idea of an uninitialized object, an 'out' parameter is OK. The idea of a parameter the function must not read before it writes is useful, and it lends some orthogonality. I still don't like it though.
❤️👍🏼🙏🏼
The simpler language inside of C++ is called Rust :P
More add-ons. It will become uncompilable in a few years.