1. 37
    1. 10

      Geez I didn’t know about any of these 3, and I first touched C++ around 25 years ago, and read a bunch of books mostly cover-to-cover

      The template disambiguator one is funny – doesn’t look like C++ to me either


      The strength of C++ is that it is a standard with multiple implementations [1]. But I also wonder if a weakness comes from that – I think the committee members do a lot of language design “in their heads” ? That is what I gather from watching many CppCon videos over the years

      It is impressive how they can do that, but I think it leads to a fairly high rate of mistakes and mis-specifications (I recall many complaints about the design of strong enums, std::optional, string_view, unordered_map, etc.)

      I also think it leads to the spec becoming very large, and adding whimsical syntax. This makes the compiler harder to write, which leads to bugs like these.

      I wonder if they were “forced” to maintain a working reference C++ compiler – an executable spec – if the spec would be shorter, and thus the language would be better.

      I have found that maintaining an executable spec in Python is a good way to design a language. I think this part of the Zen of Python is under-appreciated:

      https://peps.python.org/pep-0020/

      If the implementation is hard to explain, it’s a bad idea.

      If the implementation is easy to explain, it may be a good idea.

      C++ and Rust are obviously not following that rule IMO (in terms of semantics, not performance, which may be arbitrarily complex). Ironically I think Python doesn’t always follow it, but it good to write that down, and aspire to it.


      I like how Sutter is maintaining a reference implementation of Cpp2, which IMO is very well motivated - https://github.com/hsutter/cppfront

      Though now that I look at it, I wish the parser wasn’t 10K lines written by hand. I have also written a 10K+ line parser and I think it’s not a good way to specify a syntax for others to re-implement.

      I think Python did the right thing many years ago - write a bespoke parser generator, which is less than 10K lines, then write a ~500 line grammar in it.

      https://docs.python.org/3/reference/grammar.html

      The typical complaints about parser generators can be solved by writing your own – by designing your own metalanguage. It’s the right thing for a spec, and then people can re-implement the parser later by hand, for better error messages.

         10160 parse.h
          5506 reflect.h
          3831 reflect.h2
          2872 sema.h
          7223 to_cpp1.h
             0 version.info
         34364 total
      

      [1] Unlike Rust, where apparently gccrs feels that it’s best to copy the exact rustc code - https://rust-gcc.github.io/2024/09/20/reusing-rustc-components.html

      I also think that it would be nice to have an executable spec of Rust that’s slower, e.g. even retaining the original in OCaml

      1. 12

        Maybe a shorter way to say this is that the C++ committee should “simply” maintain and publish a test suite, e.g. like the one for JavaScript:

        https://github.com/tc39/test262 - Official ECMAScript Conformance Test Suite

        As far as I know, the state of affairs is that each compiler is responsible for its own tests, but there is no common test suite for the C++ language. (corrections welcome)

        But all my experience says that this simple fact makes the C++ language, and implementations, lower quality than say JavaScript. The “adversarial” method of testing (not writing your own tests) is effective in practice.


        As another example, there is a test suite for POSIX shell, but it’s not public. You generally have to pay for it, as part of the POSIX spec.

        Oils runs the Smoosh test suite, which is derived from it. But our own tests are even more comprehensive. They’re updated multiple times a week, not once every 5 years or 7 years. And they run against all the common shells, on every commit, in CI. (AFAIK POSIX will give you their test suite, but it would be better if they also gave you results!)

        https://www.oilshell.org/release/0.23.0/test/spec.wwz/osh-py/index.html

        https://www.oilshell.org/release/0.23.0/test/spec.wwz/osh-py/brace-expansion.html

        If you write a test suite for a language, you WILL find bugs in the specification, and implementations. The resulting quality will be better. It’s really that simple.

        (Also, we have 2 complete implementations that pass the test suite – Python and C++. One is derived from the other, though they run completely separately. This DOES find spec problems, e.g. when host language semantics leak through to the language being implemented – i.e. underspecification. This often happens for integers and floats.)

        Also, other shell authors have told me that they use our spec tests, which is great

        1. 1

          The typical complaints about parser generators can be solved by writing your own – by designing your own metalanguage. It’s the right thing for a spec, and then people can re-implement the parser later by hand, for better error messages.

          I personally feel the error messages can be pretty good even for regular use, if you make sure your grammar is simple enough. I believe a PEG with special support for infix operators (most likely using precedence climbing under the hood) would be enough for most language designs. Or you can go full Earley parsing, and use prioritised choice to deal with ambiguities. But then there’s a risk that you might use the full power of your parser, and end up with a grammar that’s much harder to hand-parse.

          What I really want is a tool that makes sure that my grammar is LL(1) everywhere except for arithmetic expressions with infix operators. I believe this’d give me enough power while making sure alternative implementation could remain simple.

        2. 4

          The first case looks similar to Rust’s lifetime extension. In general, you can’t take a reference to a temporary value like let s = "s".to_lowercase().trim() (lowercase is newly allocated, trim is a view), but Rust has one exception for expressions starting with &, so let s = &"s".to_lowercase() actually works thanks to compiler magic. This confuses people learning Rust, because code that looks like a textbook case of borrowing error actually works safely.

          1. 1

            not sure if this helps but c++20 introduced templated lambdas. when the template parameters cannot be deduced from the type of the arguments, the only way to call such a lambda is obj.template operator()<Type>(). this is something a lot of people will have tried by now or will try sooner or later, so at the very least you can expect the template<SPACE> syntax to not remain so obscure going forwards. yay?

            1. 1

              Hopefully will get fixed soon.