Microsoft is stupid enough since the first decision of the implementation impacting the ABI is made. There was no one enforcing such bad decisions shipped into the productions at the very beginning.
Both libstdc++ and libc++ have more flexible rules to preventing ABI breakage.
In some stricter sense, back-end is for target-depending stuff like ISA-dependent code generation. A great deal of work in both GCC and LLVM is in the so-called mid-end. Both have more than one IRs in the pipeline after the front-end.
This is technically incorrect. A programming language can be designed with specification in mind, even with a formal one (e.g. SML). It is just true that the specification is not likely effectively verified before more than one real implementations landed, if it is not formally verified. (Anyway, verification by testing of existing implementations _is_ the fallback where people cannot afford the cost of formal methods.)
There are simply lacking of such strong requirements in language standards. C/C++ even have the specific "linkage" concept to abstract the binary details under the source form away. And you may know, many libraries are distributed by binaries.
The standards implying binary compatibility rules are about ABI (application binary interface), which usually depend on the ISA (instruction-set architecture) or the OS (if any) being used. You cannot have the unique one once there are multiple ISAs/OSes supported. Even when you only want to rely on some external "exchanging" representations not tied to specific ISAs, there are already plenty of candidates: CLI, JVM, WebAssembly... Plus there are more than one executable (and mostly, runtime loadable) image formats widely used (PE/COFF, ELF, Mach-O ...). You will not have the unique combination, and any attempts to ensure it "work across compilers" in that way will likely finally just add a new instance not fully compatible to existing ones, making it more fragile.
It's about the ability, not the reality. As Racket, the base language can have no explicit rules for any type systems to be embedded. There can be more powerful candidates, e.g. Kernel: https://web.cs.wpi.edu/~jshutt/kernel.html.
Racket is special because its designers provide dedicated support of language-oriented programming. But that is about ecosystems, not typesystems.
Types are closed terms of contracts encoded in a language within specific phases. If you really need any guarantees without further knowledge shaped before running, then, besides the typechecking, the typing rules should also be programmable by users (rather than the language designer) for the sake of providing proofs. The base system must practically have no mandated static type systems at all, which is far from Rust.
I'm disappoint to "VM-like" behavior too, so I don't expect they would be totally replaced by WSL.
But this is similar in the other direction. Many applications just rely on such "VM-like" behavior, even though they can be (re)built for Win32 and/or they do not need a real VM for functionality. Note WSL1 can already do something more, e.g. hosting programs as X clients working with VcXsrv. I don't think WSL2 will be necessarily better than WSL1 in many of such cases. (In particular, when I have to reserve VT-x for some other hypervisors, I have no other choice.)
There are too many things to be improved, e.g. support of simple (enough) cases w/o any scriptish code, getting away of stupid syntaxes (like distinctions of indents with TABs vs. spaces) in makefiles, avoiding being relied on as a command line tool in LTO builds (grep GCC source if you don't know how MAKE is depended on), saner parallelism...
If there are not so many things to be improved, there should not have been so many tools to replace `make`.
This is simply not acceptable because it would silently break too much code. Parameter passing in C++ used to mean copy, not move.
Perhaps a more important rule is that id-expressions should mean lvalues, not xvalues. Note that even expressions of rvalue references are not xvalues in such contexts. So, making something to-be-moved visually different from others is quite intentional.
Alternatively, to prevent use-after-move cases by additional syntaxes with typechecking rules (like Rust) can be a good idea, but it also does not work here. And C++ still lacks destructive move. (Note this has been considered at the very beginning of the design. Sadly it does not easily interact well with other features. See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n137...)
Quoted from the C++ standard: "the asm declaration is conditionally-supported; its meaning is implementation-defined". ISO C does not differ much in conformance (the asm keyword is common extension listed in annex J). There exists implementation without inline assembly support, e.g. MSVC x64. There are alternative keywords used in practice (e.g. `__asm` or `__asm__ __volatile__`). Actually the contents are not supposed to be assembly language in all cases when it is supported, as I know there exists implementation with JavaScript as the "assembly" (Cheerp). Even the implementation supports "genuine" assembly, it varies in syntax, e.g. for x86, Intel (MSVC), AT&T (Clang), or both (GCC) ?
So no, you can't expect it too much, if you have to.
The problem is that pointers almost totally violate the single responsibility principle.
Technically, the only dependencies on pointers are types of the allocation/deallocation functions which are mandated in ISO C++ even for freestanding implementations (not the case in ISO C), and their derivations (new-expressions, etc). All other sane uses of pointers should be replaced by some better alternatives when appropriate (shared_ptr, unique_ptr, observer_ptr, reference, reference_wrapper without type completeness requirement, uintptr_t, iterator, etc).
In that sense every pointer is a badly designed variant type; basically each occurrence of a pointer would likely be mostly incorrect (or not correct enough) in program semantics. I always treat that vagueness as a can of worms, so even merely replacing pointer by observer_ptr somewhere makes a big win to me. There do exist real costs like verbosity and bloating of binary size, but they have to be paid for sin of the father. The ship has sailed too far in a regretful direction; but this is not the excuse to fix it - which is one source of the problem from many developers.
> Then C++ added new abstraction mechanisms on top of C, that were at first totally orthogonal: just classes and templates.
Not quite. Classes are based on struct types in C. Templates are based on classes, function types, and more.
> As C++ becomes less and less capable of doing these low-level unsafe things (because too many people caused too much damage attempting this), and adds more ceremony to the old ways of peeking and poking memory, it becomes less useful to me.
This is not the truth. It is just never supported in a portable way. And there is no other choice. ISO C is also always lack of that capability (if not more restricted). Nevertheless, you can still rely on the usefulness provided by implementations, as-is.
Microsoft is stupid enough since the first decision of the implementation impacting the ABI is made. There was no one enforcing such bad decisions shipped into the productions at the very beginning. Both libstdc++ and libc++ have more flexible rules to preventing ABI breakage.