C++20 Design Is Complete: Modules, Coroutines in C++20
reddit.com
reddit.com
https://isocpp.org/blog/2012/11/universal-references-in-c11-...
I feel I will never master C++ -- neither will Stroustrap.
https://www.theregister.co.uk/2018/06/18/bjarne_stroustrup_c...
This a thousand times. All the best languages have a good way to print out a structure, and allow interactive inspection. Instead with C and C++ we have to suffer with debuggers rather than just using good old print statements.
Printf debbuging is the actual suffering.
There is a time and space for print debugging or its equivalents, like when debugging very low level code, or when debugging rarely occurring problems.
However, using a real debugger is extremely powerful when it's available.
How do you show a table of just the information you need, to help you find a bug? What if the table is a join of multiple datastructures from distinct locations?
How do you, after stepping forward through the code until you reach a certain configuration, make a small modification to the logic, so that you can replay from the start with the intent to reach a slightly modified configuration?
Are graphical debuggers on par with simply print debugging to notify when a particularly complicated situation occurs that can only be described by a complex logic?
Edit: Since you edited your question after I replied, all those use cases are still covered by intellitrace, dtrace, flight recorder, jtags, with the benefit of not having to recompile the code.
Naturally you can come up with wicked examples that those tools don't cover, then again they will be most likely irrelevant for the large majority of devs.
I admit I don't know any of these tools, but I hope (1) they don't introduce another language than the one the project is coded in, (2) they let you put the logging statements inline in the code since otherwise the debug setup would be hard to synchronize to the actual code.
The situations I described are not particularly wicked, but rather normal debugging. If the bug is not trivial (in which case an interactive debugger can help finding it quickly), then the process is always to start with a global but coarse view, decide where the bug likely is, and zoom in on that partial view, requesting more details.
I doubt any automated system can help much there. Automatic structure printing might already not work for many cases since it prints too much data, or formats the data in a way that is too mechanical and not fitted to the task. Or in case of object-languages, the system does not now how "deep" it should print these structures.
I think automatic structure printing is a nice to have for simple situations with few structure fields. In practice I'm almost always fine with typing a printf line manually. I don't think that typing and modifying the line as I go would take the majority of my debugging time.
I use interactive debuggers myself, and I think they are faster in simple cases since there is no edit-compile-rerun cycle. But like all interactive GUIs, they are not (or only badly?) scriptable and automizable.
Stuff like DTrace and Intellitrace have OS level support to integrate with the application being instrumented, execute whatever actions are required and interoperate with the debugger. Intellitrace even goes as further as supporting time travel debugging.
Yes some of them, e.g. DTrace, do have their own little scripting languages.
std::vector<int> v = {1, 2, 3, 4}
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout,” “));
Kinda long but gets the job done.Yes, it's possible to come up with "gotcha" questions that are hard to answer for C++. There are dark corners of any language, C++ has more than many to be sure.
Yet it seems that C++ detractors love to roam around in these dark corners far more often than actual serious users of the language do. It's very, very rare that I have run into a misunderstanding of the language that has caused a bug. What you almost always get is a compile error, which in fact, is exactly what you want.
It's not the new features causing the bugs, it's the old ones.
It's uninitialised variables, dangling pointers, undefined behavior, etc etc etc.
And guess what, it's the new C++ features that are making those kinds of problems rarer and rarer as time goes on. It's starting to get pretty damn hard to use-after-free or leak memory.
Rvalue reference, the passed value either has no memory location yet or has a memory location that won't be accessed anymore (e.g. Marked with std::move)
void foo(int&& a)
Universal reference. `a` is inferred as an rvalue reference if it is initialized with an rvalue and a normal reference otherwise: template <class T> void foo(T&& a)
So && with type deduction tries to forward rvalue-ness. It does a terrible job, though, which is why std::forward exists.We need to forge forward, and support the next generation of programming languages.
In all seriousness, C and C++ are with us forever. Improving and moving these languages forward, and improving their safety with static and runtime analysis (and hardware support) has to be part of the plan moving forward.
Retrofitting also doesn't solve the philosophy of a language. There are simply aspects of C++ that are too vital to its identity to change. Is object oriented programming the final word on abstraction in programming? Probably not. This is a young field, and there are always better solutions lurking around the corner. When we buy into the notion that something should live forever, we rob ourselves of the opportunity to move forward, or to at least know with certainty whether something is truly the best.
Unless we want to repeat what has happened with COBOL, where the systems have lasted forever to the point that all of the COBOL programmers are dead or retired, we need to start evolving our philosophy to favor language replace-ability, or stop guaranteeing backwards compatibility. The latter is untenable to most businesses, while the former can be achieved through the use of small services, FFIs, RPC, and system modularity.
I don't claim that existing systems are easy to replace, but I do posit that they can be made replaceable given that certain practices are adopted, and the mindset of the developers is that they system should be easy to remove and replace.
I also wouldn't say that Go is invalid, Go is more than production ready; it's actually in production in critical infrastructure today at scale.
Moreover, COBOL story just reminds me that there are still many cases that code should be maintained forever, and there is no unicorns to solve all problems like a magic. RPC introduces performance and mental overhead. FFI is limited by performance/vague boundaries. Even in backend development, there are lots of companies switching back to monolithic application after trying microservices.
It's also unfair to dismiss RPC or FFI, which have come a considerably long distance, and in many cases add no noticeable overhead. Today's networks are now approaching 400G speeds, and Linux has added considerably more interfaces such as eBGP bytecode that rewards a cross language mindset with better performance.
The problem of RPC is latency & increased complexity, not throughput. It's still hard to achieve native performance when passing objects between FFI boundary to avoid a copy, especially when using stub generators.
I choose languages through a series of criteria. First, based on my principles, which limits me to languages were safety, efficiency, and expressiveness are highly valued. I find that most languages have some core principles listed on their website. Then I take my software's requirements, and find a language in my subset of choices that is a best fit. If I am on a team, then we have to come to a consensus on the teams shared principles, which I find is very useful outside of choosing a language.
I think that services can be sized sufficiently large to be replaceable and still encapsulate most critical code paths such that sharing memory should be a rare requirement. Facebook and google are both examples of companies operating at hyper scale based on RPC service architecture. For those times were it is a requirement, I can't argue that RPC is a good choice. C is a great lingua franca, and most languages support its ABI (at least the ones used for critical performance).
As for C++ interop, ObjC++ is the best I've seen; is there something better?
If you believe that C++ "sacrificed" for backwards compatibility (and I agree), well what was that sacrifice for anyways, if not better compatibility?
It is a much better interop story to use this type of FFI. As I've previously said, C++ compilers are usually not 100% compatible with their C counterparts, and will actually change the semantics of attributes and make different decisions about inlining. It is far better to use a C compiler to get machine code that matches the intended semantics, and call over a well formed ABI that to take the subset language approach.
Not to mention that using C libraries from C++ adds a different convention for memory management in the middle of what should be RAII code, which almost always leads to memory leaks. By using the Rust FFI, Rust will at least make sure you are treating unsafe code in a controlled manner.
Like in any other language with similar feature, it is still a pain to manage in large APIs.
Here's a real example. If you write C, you'll probably make a system call, and then check errno. You might also write to errno, e.g. save and restore.
errno is implemented differently on every platform, but a C program can include errno.h, and use it portably. C++ can do the same thing: include errno.h and you're done.
But Rust can't read the C header, so it must literally special-case every OS [1].
errno is just one thing, but there's a long tail: different platforms have different types and size and function names and signatures, especially the ancient system-y stuff.
"Rust is better than C++ at C interop" is an absurd take. The point of Rust is to displace this stuff, not be the best at integrating with it.
1: https://github.com/rust-lang/rust/blob/7cb3ee453b829a513749e...
> But Rust can't read the C header, so it must literally special-case every OS [1].
That's a bit of an odd point. You're saying that the C standard library implementation of errno is different on each platform, but abstracted in the standard library so that C programs can just import it and run a portable function.
...and then you complain that Rust does the same...
Tier 1 is C++ and ObjC, where you can use C headers nearly directly.
Tier 2 is where you have to create (and maintain) bindings, but you can map practically everything. Here is Rust, D, Nim, Go, etc. More work than tier 1 is necessary but no loss of performance (except maybe inlining and macros). There are distinctions within this class depending on how well macros etc can be mapped, but usually people map to the ABI instead of the API.
Tier 3 is where you cannot map directly and inefficient adapters must be made. This is for example Java, which does not have struct or size_t. Also nearly all scripting languages (Python, Ruby, Lua, etc).
In rust, you can do dynamic dispatch via trait objects over C structs. In C++, that wouldn't work, because dynamic dispatch uses inheritance and it needs to add a virtual method table pointer in the struct, meaning that you'd need to translate/copy the struct into a C+ one.
Also, rust has neat tricks. An Option type of a reference is represented as a single pointer where NULL means None. That means your plain old C struct can be interpeted as an Option type in rust without copying/translation.
1. In Rust, you can define a method on a trait object (a type that encloses a C struct), and then invoke it dynamically with the syntactic appearance that 'self' is the struct. C++ does not allow you to add virtual methods to C structs.
2. In Rust, given a C struct T, you can convert T* to an Option<&T> without copying.
This stuff is all gravy but I wonder about the meat. Is there a good story for consuming C headers directly? C structs are typically a hash of typedefs and preprocessor gunk; can Rust navigate that?
class cpp_class {
c_struct *pimpl;
public:
cpp_class(c_struct *p) : pimpl(p) { }
virtual void whatever();
}
class derived_cpp_class : public cpp_class {
public:
derived_cpp_class(c_struct *p) : cpp_class(p) { }
virtual void whatever(); // override
}; // Just copy paste this
template<typename T>
class CContainer {
protected:
T *pimpl;
CContainer(T* raw_ptr): pimpl(raw_ptr) {}
public:
CContainer(const CContainer&) = delete;
CContainer& operator=(const CContainer&) = delete;
virtual const char* get_my_name() = 0;
virtual ~CContainer() {};
};
// User
class MyCHandler : public CContainer<c_struct>
{
public:
MyCHandler(int arg) : CContainer(my_c_new(arg)) {}
virtual const char* get_my_name() override {
static const char[] kMyName = "MyC";
return kMyName;
}
virtual ~MyCHandler() {
my_c_destroy(pimpl);
}
std::unique_ptr<MyCHandler> clone() {
return std::make_unique<MyCHandler>(my_c_clone(p_impl));
}
};
Moreover, the zero-copy magic from pointer to option in Rust is not something like what you think - Rust relies on compiler magic to eliminate option type's heap allocation, and if you convert it from a pointer, it will reside on the heap.I'm sure that you can construct the container object at each call site so that it behaves like rust (and it would just be allocated on the stack), but it would get a little ugly. In rust, it's very seamless and "rust-like" even though you are operating on a native C struct.
Similarly, the Option type magic is pretty darn useful. I'm sure you could do that in C++ as well, but again it would end up ugly because you'd need to invent a new COption type or something.
In the general case, the hardware requires it; that's how computers work. The C library you're working with that has handed you the structure has not reserved any space for you to put anything into the C structure for it to have embellishments like type-dispatched virtual functions. The memory addresses immediately above and below the structure are off-limits (if they exist at all).
If you control the allocation and disposal of the memory, then you can easily embed the structure in a larger object. When calling the C code, it just gets a pointer to that part of the object that is the C structure it knows about. Amazingly enough, C++ can do this quite easily. It can be done not just via composition, but also by inheritance:
class derived : public some_c_struct {
public:
virtual method();
};
Then it's just: extern "C" void c_function(some_c_struct *);
{
derived d; // define easily "on the stack"
c_function(&d); // pointer upcasts to base implicitly
}
All you see here is 1995 draft vintage C++.The idea that C++ doesn't work smoothly with C structures is bizarre; C++ contains a dialect of C which is highly compatible with C90 (to the extent that large programs can be developed such that they compile as C or C++).
In rust, it creates the trait object right as it's calling a function that requires one. But "creates" is too strong a word -- it's just sending the virtual function table pointer along with the structure pointer and any other arguments, probably in registers. Only if you are actually storing the trait object somewhere does it matter much.
In your example, you have to construct the "derived" type each time you come from C to C++. Using an extra type is annoying. I think a better C++ example would be trying to do something closer to what rust does and use the C struct pointer most places, and construct the C++ object right before you need it. Would work, but doesn't come out quite as clean as it does in rust.
In any case, the main point is that the normal mode of C++ is to add vtable pointers to the structures to achieve dynamic dispatch, and adding stuff to the beginning of a struct does not interoperate with C code very well. The normal mode of rust is to not touch the struct layout and achieve these features through other means. I'm sure there's a way to work against the grain in C++ and make it work, but if you ask me, rust gets it a little cleaner in this particular case. Or you could argue that working against the grain is the normal mode of C++ ;-).
What does this mean? A struct is just a block of memory with zero type information. So how does Rust do dynamic dispatch on C structs again?
Which is perfectly doable in C++ too.
As managed languages dev, I seldom use C++, but when I do it is the best system language currently available regarding tooling, libraries and hardware support (NVidia now makes GPGPU optimized for C++ semantics).
For app development, there aren't many scenarios where C++ is really necessary, whereas for systems it is going to stay around for years to come.
So anything that helps improving the quality of GCC, LLVM, CUDA, UWP, Metal Shaders, DirectX, Unreal/Unity/CryEngine/Cocos/Godot is more than welcome.
For anything else, we can just follow Microsoft's security suggestions as presented at Blue Hat IL, namely C# (replace by favourite managed language), Rust and modern C++.
https://github.com/Microsoft/MSRC-Security-Research/blob/mas...
[1] https://blogs.nvidia.com/blog/2019/02/05/adacore-secure-auto...
They also support initiatives to have Java, .NET, Julia target their GPGPUs.
And they have supported Fortran since early days as well.
Which is why OpenCL by being so focused in plain old C, with manual compilation, never got that much love from researchers.
For cross platform work, C++ is hard to beat and no languages comes close when it comes to tooling. For example, I can debug from Objc into C++ using XCode or Java to C++ in Android Studio.
You don’t necessarily have to use every C++17 feature…
Also, a large part of modern c++ is that simplicity is not only better for people but for compilers.
A lot of it is using the type system to help you or better describe your intent. Putting your values invariants into it's type means we can guarantee them and not forget.
Use RAII. This is the biggest and it has been here since the beginning.
Yeah, the old polymorphic class hierarchies can be tough. Who owns this pointer I have right now? One hopes the library takes ownership, but a pointer type doesn't have that invariant or ownership. This is where unique_ptr really shines. Also, one can create a type like poly_value<Base> that can be a Base or Child of Base and then treat it like a value with copies and all if the currently held type supports it.
But yeah, lots of people are working with 20 year old code and can at best do boyscout rule to clean up what they touch. There are a lot cppcon/c++now like talks about working with these.
So anything that's to cross the ABI boundary must be wrapped in vtables with methods taking/returning primitive types.
Of course, you have to unwrap it on "the other side", and the more such wrapping/unwrapping you have to do (casting to/from void* or intptr), the higher the chance that you'll mess it up somewhere along the lane.
This naturally leads to OO design from the start and "functional techniques", if any, are buried deep in the implementation code. Though they make awful compile times and debugging experience is awful as well (ever tried to inspect a boost::fusion::map in a debugger?).
When a language contains lots of ways to shoot yourself in the foot, it's just not realistic to expect that those features aren't going to be misused when potentially millions of programmers might be coding in it. Even getting agreement of how to use features within a small team can be difficult, and you're always going to have to use external libraries developed by others that don't follow your coding standards.
The language design should make it easy to do the right thing and difficult to do the wrong thing in my opinion. When the language isn't designed this way, it's always an uphill struggle where the coding community has to devise and enforce their own standards.
I am of the mind that any language that is useful for general purpose or at least outside of small niche areas will provide many ways to shoot yourself in the foot. C++ does have a lot of them, but it also has a lot of tools.
https://herbsutter.com/2019/02/23/trip-report-winter-iso-c-s...
I wonder how long it would take to learn C++ now.
With C++, in spite of its warts, I can still be productive just because of the ecosystem around it. Rust was something I was gung-ho about, but after repeated attempts to create a medium/large project, I am still in a wait and see mode. Moreover, it's just not powerful enough. Many features are coming in, but they break the compiler and you end up needing to work off nightly regardless. I think it's a good effort, but people just recommending the Rust train nonchalantly really need to step back and check their biases.
My original point was that we shouldn't be building monolithic projects in a single language, but if there is an absolute requirement for it, Rust compile times aren't that bad. C++ also has very long compile times, it just comes with the territory. The reason Go can compile in under a second is because they purposefully keep the language specification simplistically small.
I'm not recommending Rust nonchalantly. I write rust every day professionally, and I basis my opinions on the experiences I've had with it in that environment.
The reason Rust was invented was to make the monolithic firefox executable to have multiple tabs that don't block each other and can act concurrently. All of this has already been solved in a much cheaper and already available way: Unix processes.
How much RAM did the machine have?
(Oh, and you’re wrong about the Firefox thing as well.)
For instance, take a look at the things you have to do to get perfectly reasonable-looking code to compile if Non-Lexical Lifetimes are not allowed:
https://github.com/rust-lang/rfcs/blob/master/text/2094-nll....
[1] https://build2.org/article/cxx-modules-misconceptions.xhtml#...
https://www.reddit.com/r/cpp/comments/au0c4x/201902_kona_iso...
And is there big difference between the latest version and C++ from 5 or 15 years ago?
How do you guys keep up when a huge language is continually changing?
Chip designer: In a high-level language like C++...
To answer your question, one of the motivations behind the (ongoing) design of C++ is to factor out and codify common C/C++ coding idioms. Almost every feature, library or language, can be traced back to this motive. So most "new" features are less so "new" and more so "that thing you're doing informally? It's formalized now." E.g. concepts codify how template parameter requirements have traditionally been stated.
But as with anything C++, you and your team need to decide on a per-project basis on how far down the rabbit hole you are willing to go. There is a world of difference between C with classes and metaprogramming parser generators. And to the standards credit, a lot of the new stuff can be used individually.
C++14 and 17 mostly just add some library features and clean up rough edges. You'll know you know C++11 well when you start wanting those features.
Only then look at C++20. Bonus: there might actually be compiler support for it by then. (Caveat: You might want to learn modules sooner; they will probably be the most visible day-to-day change.)
Its getting modern, but its no where near perfectly. There are three different ways to allocate heap data and at least one dates back to the 70s. There are at least five ways to initialize a variable last I checked. The language is ludicrously complex to parse for both a human and a computer because its been iterated on for so long.
Because the design space C++ occupies is so complex and lacking almost any feature of it kills competition Rust is about the only real competitor in the space of "modern language that does absolutely everything" besides peers like OCaml that often have too arcane a syntax for the C family linguists to get in to. But compared to C++, Rust is way more cohesive just because it started one decade ago rather than four and has much less baggage so far. And with its editions system will likely never have permanent unmitigated baggage to its benefit.
I've found code where developers casted perfectly useful data structures to void* for no reason to then index into it by memory offset to access its fields, I've seen well architected concurrent classes destroyed by consumers "friending" it and then directly addressing internal private data meant to be guarded via accessor functions, and more.
C++ gives you infinite ways to shoot yourself in the foot, and even if you are the savant genius god programmer who manages to navigate its neck deep swamp of complexity your coworkers or even worse random strangers you collaborate with on the Internet absolutely won't.
C++ and C have both been around for ages. When they first arrived, most (all?) computers were single threaded and therefore neither of them put threading utilities in the standard library and in updates to both languages, now C and C++ both have libraries for dealing with multiple threads (and I believe an improved memory model that takes threading into account.)
Almost all programming languages evolve over time. Programming changes, computers change, and people gain experience and figure out what the pain points are in a given language. Naturally, we take what we learn and apply it to future language versions.
- auto var = eval()
- add_observer([this](int value) { this->log(value); }); // lambda expression
- vec2 = std::move(vec1); // everything in C++ by default is value-type, so this saves a copy
- for (auto&& [k, v] : treemap)
- auto ref_ptr = std::make_shared<SomeValue>(42); // ref counting
- auto unique_ptr = std::make_unique<SomeValue>(42); // non-sharing, uniquely owning ptr. can only be moved
- module foo; import "some-header.h"; export something_from_header; export void your_own_func();
In reality these are 90% features you need to migrate from C++98 to latest version. Don't be terrified by thousands pages of paper, just use them as a reference when you need to.
And yes, there is a big difference between C++ 03 and C++17 and an even bigger difference from C++20 but C++ has done a good job of maintaining backwards compatibility so almost all C++98 code will still compile with a C++20 compiler.
How does anyone keep up with the changes in technology over the last 20 years? At least with C++ much of your old knowledge remains relevant, more so than a Visual Basic programmer who is now using JavaScript with the latest UI framework flavor of the month.
I'm just happy I actually make/simulate things for a living than worrying about learning new tools I didn't ask anyone for.
As compared to 15 years ago, the language is almost unrecognizable. As compared to 5 years ago, it's a bit more convenient to use, but not wildly different. C++ is a relatively old language, but it didn't have an real standard until 1998, and even that version was more about documenting what was out there rather than prescriptively designing anything. Before that (and to a lesser extent since, cough-cough-Microsoft-cough), it was a mess of incompatible OS features, compiler-specific semantics, and inconsistent tooling. In the right environment, it could be the most amazing and productive language, but it was a very short trip from "this is great" to "here be dragons and assorted hellbeasts".
C++11 has been by far the biggest change. Beyond the huge impacts on the language itself, it was the beginning of the current standards cadence of new standards every 3 years. To point to a single feature, C++11 introduced smart pointers and move semantics, which are the only "modern C++" features that I use in almost every single source file.
>How do you guys keep up when a huge language is continually changing?
A new version every 3 years is not that fast, really. And the new features are totally opt-in and backwards compatible; code written decades ago will, for the most part, still compile today. That means that you can slowly adopt new features one at a time as they make sense.
>why a low level language is going through so many new versions?
First, C++ isn't really a low-level language - or at least, it doesn't have to be. It hits a really interesting sweet spot where you can express powerful things concisely, and yet predict more or less exactly how things will execute if you think it through. It doesn't run on a VM like Javascript or Python, and it doesn't have the esoteric abstractness of Haskell, but it's certainly much higher-level than C, and there are a number of layers farther down than that.
Second, the reason for the iteration mostly has to do with filling in features that everyone had to build for themselves, and which were often platform-specific. Each version has progressively expanded the standard library with things like cross-platform concurrency primitives (std::mutex, std::thread), file I/O (<filesystem>), efficient string manipulation (std::string_view), and improved templated containers (std::optional, std::variant). Adding smart pointers in C++11 was about moving best practices about memory management into the language. Adding file I/O in C++17 was about standardizing cross-OS compatibility layers. Adding modules in C++20 is about improving standard buildchains and package management. Prior to C++11, every organization had to have their own implementations of all of those things, which required a massive investment into internal "standard" libraries, styleguides, and tooling, which were totally incompatible with each other. In the modern C++ era, a lot of those things can be the same for everybody, which makes everything more efficient - easier to transfer to a new codebase, easier to start from scratch, easier to share tools and libraries.
That was the original intent, I think, but decision to incorporate the STL caused the standard library to change massively late in the standardization process.
P.J. Plauger wrote a book on the 1995 version of the C++ library, that ended up being massively different than what the standard adopted: https://www.amazon.com/Draft-Standard-Library-P-Plauger/dp/0...
Coming from Turbo Pascal 6.0, it provided me a world with access to C based tools, without having to endure typical C unsafe code.
Every major language evolves over time. Java 8, ES5 Javascript, Python 3 were all pretty major iterations of each language. Similarly, C++11 went as far as Java 8 in the number of things that it got up to "modern" standards of convenience and power. The goal seems to be towards offering some of the conveniences of heavier or more high-level languages, but with the power of a low-level language.
> And is there big difference between the latest version and C++ from 5 or 15 years ago?
From 5 years ago, probably not. 15 years ago, C++98 was likely to be the flavor of C++ being used. C++11 was still in early draft mode, and was called C++0x. Today, C++ can look like a dynamically typed language, e.g. using "auto" type inference for variables, lambdas, and range based loops. If you follow something like the Google C++ Style Guide, you'll never see a raw pointer (C++11 introduces reference counted smart-pointers).
> How do you guys keep up when a huge language is continually changing?
Major language iterations are generally pretty uncommon. Beyond that, following a good style guide that is maintained by other people.
C++ aims to be the high level language which leaves no room for another high level language to be even more optimized below it - “pay for what you use”.
Having its roots in C, while having high level abstractions, C++ also has a lot of low level functionality which is hard to use correctly and safely unless you have a lot of experience (e.g. raw pointers). This low level functionality is sometimes needed, but definitely not always, and the recent iterations of the language over the last decade try to both add new abstractions which make the language easier to use, and deprecate the most tricky parts which are not really needed anymore and which have better, modern alternatives (this being limited by the need to maintain sane backwards compatibility) - all the while maintaining C++’s design goal described above.
An example of an addition is unique_ptr and shared_ptr added in C++11 and which make managing ownership and correct lifetime of allocated objects much easier.
An example of a deprecation is eliminating gotcha uses of the ‘volatile’ keyword, and thus simplify the language. (I believe this one is still undergoing approval.)
There is a big difference between modern and “classic” C++, but it’s mostly a good difference - C++ is much easier to start using and to teach than it was 15 years. It still has a way to go, and the standardization committee makes a lot of effort in that direction.
As for keeping up - it’s definitely a lot of work. Learning C++ isn’t something you start and finish - it’s more like culture. You spend some part of your life studying and enjoying it, and there’s always something new.
We can hope for a future in which learning C++ is something you just start and finish, but we’re not there yet (if ever). I’m not sure if that’s a good or a bad thing :)
If you can, I would learn Rust instead. Rust is a much smaller language that purposely limits the way you use the language to encourage particularly safe practices, and tends to favor designs that are hierarchical instead of allowing hairball object graphs. Rust also guides you at the compiler level in ways that C++ programmers must learn to do by heart, or simply leak memory and create nasty patterns in their code.
https://herbsutter.com/2019/02/23/trip-report-winter-iso-c-s...
The papers aren't available yet but presumably will explain the reasoning behind accepting them in their current form.
I remember vaguely that there were some issues with the initial module proposal and multiple (concurrent :) coroutine designs.
Does anyone know what the problems were and how they were resolved?
I feel like with the addition of modules this may be a release of the language spec that is going to need at least a decade to settle in. I hope the next few changes are more incremental.
I am very excited for modules. Hell, maybe I'll even see them used some day in the chromium code base I work in.
I am aware that new features means a few parts needs be rectified. But does the whole need to change?
And I agree for C++ compiler writers to be gods. Looking at the source code for GCC/clang makes me doubt my programming skills. Same goes for the linux kernel source code.
And the parser is hard in pain-in-the-ass terms, not CS theory terms.
https://raw.githubusercontent.com/gcc-mirror/gcc/master/gcc/...
foo * bar;
This is either an expression statement computing the product of `foo` and `bar` or the declaration of a variable `bar` as a `foo` pointer.
To solve this ambiguity, you need context: if `foo` is a type available in the current scope then it's a variable declaration, otherwise an expression. Some C++ constructs require arbitrary look ahead to disambiguate.
For example, it is impossible, in the general case, to tell whether
a statement is an expression or declaration without scanning the
entire statement.
I am sure there must be some devilish source code examples that require an exponential search for the correct parse. Also, has anyone constructed a modern C++ parser in bison which can backtrack?It already takes years to really master C++.
Soon you will be unable to read and understand C++ code without googling every second line.
I have a love / hate relationship with C++ — without the love.
I’ve always despised C++ but that’s when it was pretty similar to C. It seems to have gotten exponentially more complicated but also it seems like it’s pretty expressive at this point: potentially supporting Rust-like safety and FP-like design.
Maybe someone who’s an expert can tell me how different C++20 is from C?
Of course the 'c++' way is doing event listeners and recording data when it's changed.
With Qt you can chain QObject::metaType(), QMetaObject::className(), int QMetaType::type(const char *typeName) to get an integer you can use to switch () on type. It is, I believe, possible but obscure on purpose because it is not meant to be used willy-nilly. You can also access Q_PROPERTYs by name using QObject::property(), and Qt's own classes have the same names for the same properties where it makes sense - such as text() in QLineEdit and QAbstractSpinBox etc.
It depends on your requirements if using that approach actually makes sense. I haven't seen it used yet... If you want to serialize GUI state, a more intrusive design where, say, you keep some helper data on the side to allow "dumber" code in the actual de/serialization might be better. Or just wire up the widgets to a model (QAbstractItemModel and / or QObjects exposing Q_PROPERTYs - that's how you do it with QML if you do it right) and use the model as the main source of truth and, of course, for serialization.
The "high level debugger for Qt" GammaRay is a nice showcase of what you can do with Qt's introspection capabilities. Qt had ~3 extension points added for GammaRay to observe object lifecycles, most everything else is based on pre-existing functionality.
To keep things far simpler, the .UI file is meant to be the one that defines the premade widgets, and have properties on them. How UI seems to be usually done in QT is uic generates a header and the object names become members that can be used directly. Then all those members are used directly, and become hard coded.
In this circumstance though, I don't want to hard code in every widget, that way if new options are added, they can be updated in the .ui file and not require recompilation and more hard coding of widgets.
Problem is, you can't do qwidget->find, get the head widget, then get a list of the widgets actual child type. What I've read and have asked others on, you gotta qobject_cast until it sticks. C++ can't get types at runtime and then cast them, it has to know what to do at compile time. Hence having to write out every type and cast it.
On the one hand, C++ has created generations of Visual Studio GUI addicts. Imagine how much further software would have advanced by now if the average person (a Windows user on a crappy laptop) had good, free tools to make web and mobile applications. Two things VS and C++ are just really bad for, if you inhabit reality.
And all those stubborn people who are going to reply insisting otherwise, how much better would the world be without them? Just kidding guys, hang in there.
On the other hand, less competition! Long live C++!
C++ has been used outside of Windows since basically the inception of C++.