Aside, a pox on the house of bottom up parsers. Anyone trying to produce useful semantic errors out of a bottom up parser understands how god awful they are to work with. There's a reason both GCC and clang use hand rolled recursive descent parsers...
I have yet to see a reasonable explanation why C++ needs a type-aware compiler to accomplish its goals. I can't think of any other languages with this problem.
Are you saying strong typing is a problem with C++ as a language? Or am I misunderstanding?
T *p;
It means one thing if it were preceded by this, making the line a variable declaration: struct T;
typedef struct T T;
It means quite another if it were preceded by this, making the line an expression: int T=5,p=0;
(This particular problem also affects C.)From a practical perspective, this makes the compiler code more complicated, because now the parsed code has to be analyzed and the results fed back to the loop. As well as the obvious negative effects of code that's more complicated, this also means any code analysis tools that work with source code have to themselves do this same work. (For C, this probably isn't too much of a difficulty, but it's still annoying having to keep track of typedef names and so on just so you can parse it! For C++, it's a very big problem indeed, on account of how much stuff you have to keep on top of - and that's why C++ source analysis tools basically never worked properly once you got past a certain level of complexity until people started just using the compiler to do it.)
I don't have an in-depth background in this stuff, so it's possible there are also more abstract benefits from having a grammar that doesn't suffer from this sort of problem.
Compile time speed? Refactoring? Jump-to-definition? Autocomplete? Intellisense? Those are all important for UX, and a tricky-to-parse grammar makes those things mighty challenging.
Another thing about recursive descent, besides the ability to generate very specific error messages, is that it's a naturally intuitive algorithm which is very similar to how humans mentally parse. Perhaps that's also why context-sensitivity is not a problem at all, since natural languages are AFAIK all context-sensitive.
Parser generators are complex, generate huge amounts of virtually undebuggable code, and require learning another language, yet are less flexible than recursive-descent. It's clear why they've fallen out of favour in comparison to simpler and more flexible parsing algorithms.
So a language can be hard to parse unambiguously for machines, but it can be pretty straightforward for humans - like pretty much any natural spoken language.
https://en.wikipedia.org/wiki/List_of_linguistic_example_sen...
Until you realize that there is rarely any proper tooling around the language, which would be really helpful to humans!
I can list clang and cppcheck on Linux, but nothing else so far. Some of the clang related tools still choke on some parts of C++ though, so even that isn't close.
(x)(y);
Is that a call of function (or function pointer) `x` with argument `y`? Or is it a cast of `y` to type `x`? You need to have kept track of of all the typedefs in the code prior to that point to know. x * y;
Is that a multiplication, whose result is being thrown away, or a declaration of a variable y of type pointer-to-x?True, templates came much later, but by then C++ was already hopelessly unparsable by LALR(1) parsers. But in truth, it was this way from moment 0, since it was based on C-syntax. Making it parsable by yacc was a lost cause, since yacc followed C and not the other way around.
vector<int> years;
list<string> names;
is pretty easy to wrap your brain around.That's not the C comparison, that's a Python chained comparison. A C comparison will parse this as `(a < b) > c`, and you'll get false for a=3, b=5 and c=1.
Also IMHO a<b>c should not exist as a comparison. Does this mean a<b AND b>c? Then spell it out, it's a rare case and it doesn't contribute to readability (as opposed to a<b<c for example)
Both are probably broken anyway. The first one will compute as (a < b) > c, and the second one as (a < b) < c. Unless you're using Python which has chained comparison, which has its own pitfalls e.g. `a < b == c > d` may not do what you want (it's equivalent to `a < b and b == c and c > d`, not `(a < b) == (c > d)`)
Donald Knuth summed this up much more elegantly than I could ever hope to do so, in his six character critique of C++:
a < b > c;
What the hell does that mean? What it means is that you cannot determine the meaning of those six characters without knowing in advance the types of a, b, and c. Is it an odd-but-valid C comparison? (a is less than b and b is greater than c) Or is it a template declaration? You cannot know these things without context.
Back when I studied compiler theory and was reading the Dragon Book, the simplicity, elegance, and precision of the LALR(1) parser design really touched me. And it was implemented on every Unix box with lex and yacc. This is how you write a compiler! This is how you understand structure precisely! To be syntactically valid, a statement must have one and only one meaning.
C++ cannot be parsed without interpreting variable types at parse time. LALR(1) does not work. It never has worked. C++ has been broken on this front since the 1980s, when it was just a preprocessor that cross-compiled to C. Why? What possible benefit is there to giving up that core bit of parser discipline?
"Because it's powerful!", someone sneers, as if that condescension justified bad design. "Because templates are so hard!" another whispers. But what about this?
a [< b >] c;
If the template declaration tokens were [< >] rather than < >, they wouldn't overload the comparison operators. The meaning of Knuth's example would be unambiguous, and both could be parsed without grief - both by machines, and by human eyeballs. This isn't difficulty. It's just not thinking through the consequences.
But that's okay. Smoke enough of that << and >> crack, and you'll soon forget those tokens are bitwise shift operators, not i/o. Sigh.