5 ms·
> Even in that case it would be reasonable to opt in that syntax for some blocks of code, without breaking any existing code which hasn't opted in, and this "ed
by jmgao 3y ago
> Even in that case it would be reasonable to opt in that syntax for some blocks of code, without breaking any existing code which hasn't opted in, and this "edition" mechanism was very successful to evolve syntaxes and often semantics in recent languages (JS, Rust, ...).
You can't really do this in a language where the way you use libraries is by textual inclusion of headers which can include arbitrary code (no one uses modules).
- tialaramex 3y agoThe proposal (Epochs) to do this in C++ even says it needs modules. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1881r0.html https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p18... It would have been extremely difficult to do this for say C++ 20 even if the other work was abandoned (which IMO would have been sensible) and I feel confident it is now impossible.
- lifthrasiir 3y agoThat's a bad excuse. In fact I think editions are possible without modules at all, if you are really willing to fix this issue. Tying epochs to modules suggests that they don't think this is a huge issue to start with. One possible design is to assign editions to all preprocessed tokens. This implies the edition definition should be some sort of pragma (say, `#pragma STDCXX EDITION 2023`) and preprocessors should add pragmas themselves if tokens with a different edition are transcluded into other tokens. Implementations with integrated preprocessor and parser may also use a simpler internal representation to convey editions as well. Now `_` would be a normal identifier in past editions and a placeholder in a new edition, however they are passed through macros. For example: #pragma STDCXX EDITION 2023 // Hereafter `_` is parsed as `_2023` internally #define GUARD() auto _ = make_guard() #pragma STDCXX EDITION 2020 // Hereafter `_` is parsed as `_2020` internally { GUARD(); GUARD(); // Okay, equivalent to `auto _2023 = make_guard();` foo(&_); // Up to the proposal author, but P2169 suggests that // this `_` should refer to the second `_2023` } auto _ = make_guard(); // Okay, equivalent to `auto _2020 = make_guard();` auto _ = make_guard(); // Nope, `_2020` can't be defined more than once in the same scope Of course there are more questions to answer. For example what would `#` and `##` do to editions? Should the current edition reset after `#include` finishes? (I think so.) How would we compute the edition for specific syntax from constituent tokens? (A systematic approach would be to collect a set of all editions used in syntactic tokens, and if not all editions in that set result in the same result, to issue an error.) But I still argue that this design is simple, checks all checkboxes as long as open questions are decided, and can be independently deployed. And this token-level tracking is not something alien, it's required to give more accurate diagnostics anyway---and other languages like Rust have even used it to implement things like macro hygiene.
- jmgao 3y ago> But I still argue that this design is simple, checks all checkboxes as long as open questions are decided, and can be independently deployed. You're handwaving a ton of complexity. You don't even need token pasting for this to become a confusing mess, just consider what happens if GUARD takes the name as an argument, or if an expression is constructed by putting two macros with different editions next to each other. You're also missing the biggest issue, which is 'what happens with headers that don't care about editions, and only begrudgingly even include an extern "C" for C++ users?'. > (A systematic approach would be to collect a set of all editions used in syntactic tokens, and if not all editions in that set result in the same result, to issue an error.) This does not work in general. Suppose you wanted to remove most vexing parse in a new edition: changing the result of `Foo bar(std::array<int, PAGE_SIZE>())` is the goal!
- lifthrasiir 3y ago> just consider what happens if GUARD takes the name as an argument, The name token will be assigned the edition of the originating position. #pragma STDCXX EDITION 2023 #define GUARD(var) auto var = make_guard() #pragma STDCXX EDITION 2020 GUARD(_); GUARD(_); // `_` is a normal identifier, so this will probably error. > or if an expression is constructed by putting two macros with different editions next to each other. Let's replicate the removal of implicit conversions from the Epochs proposal then. #pragma STDCXX EDITION 2020 #define MOVE move #define LPAREN ( #define RPAREN ) #define COMMA , #define P p #define LIT_3_42 3.42 #define LIT_2_49 2.49 #pragma STDCXX EDITION 2023 void move(Particle&, float x, float y); void foo() { Particle p{}; // error, all tokens are in the 2023 edition move(p, 3.42, 2.49); // depending on the proposal, at least one of them // should work because all constituent tokens for // the call expression are in the 2020 edition. // proposal authors would then be responsible for // which tokens are considered "constituent" here. move LPAREN p, 3.42, 2.49 RPAREN; move LPAREN p COMMA 3.42 COMMA 2.49 RPAREN; MOVE LPAREN p COMMA 3.42 COMMA 2.49 RPAREN; MOVE LPAREN P COMMA LIT_3_42 COMMA LIT_2_49 RPAREN; // assuming the call only regards `(` and `)` as // constituent tokens, the following call has // ambiguous editions and will fail to compile. move(p, 3.42, 2.49 RPAREN; } So my chain of logic is that, we ideally want to put editions to the AST, but the existence of textual inclusion means that edition should also exist in tokens and the AST node's edition will have to be computed from them. Not a big deal for existing compilers (they already track spans). > what happens with headers that don't care about editions, and only begrudgingly even include an extern "C" for C++ users? For this reason I believe the edition should reset across files, reverting to the default edition. And in fact this feature will benefit both C and C++, and thus ideally be harmonized. In this way we will no longer have to special-case some files to be implicitly in the `extern "C"` block! (Yes, this exists, see for example -internal-externc-isystem in clang cc1.) > Suppose you wanted to remove most vexing parse in a new edition: changing the result of `Foo bar(std::array<int, PAGE_SIZE>())` is the goal! Now this is probably a better question, because we have tons of options for how existing parsers should behave. Note that I've intentionally omitted this part because those options are only useful when the ambiguity arises, and I think they can be determined in the case-by-case basis. But if you indeed want to remove the most vexing parse, it's mostly up to the proposal author's decision. I should note the following quote from ISO C++: If the statement cannot syntactically be a declaration, there is no ambiguity, so this rule does not apply. The whole statement might need to be examined to determine whether this is the case. [1] So the parser should have already produced two possible ASTs for the same token sequence in the first place and picked one of them, in principle. The proposal therefore will determine which token affects this decision---for example that may be `(` and `)` as in the call, or the whole `std::array<int, PAGE_SIZE>()` sequence that can be parsed as a type-id. In any case the proposal will give a concrete and compatible algorithm to remove the most vexing parse in a new edition. You can't remove it in existing editions, and this is by design. (Alternatively and probably more commonly, the parser may use a prioritized choice, i.e. trying to parse type-id first then initializer-clause if it fails. In this case parsing type-id may have to signal that some constituent tokens are of a new edition and backtracking might be needed. The proposal author would want to experiment with different choices of constituent tokens to ease the implementation. But again, parser should have already implemented backtracking so this should be not a huge cost.) [1] https://timsong-cpp.github.io/cppwp/n4868/stmt.ambig https://timsong-cpp.github.io/cppwp/n4868/stmt.ambig (this is not exactly about the most vexing parse but both cases are using the same wording)
- Asooka 3y agoYou can just say that this feature is only local to the current file and does not affect included files, nor files that this one is included in. There are already some preprocessor features that act like that, e.g. __FILE__, which contains the name of the current file being parsed and obviously is local to it; or "#pragma once", which indicates the current file should not be parsed if it has already been included once. The information for which file is being parsed currently is not lost after the preprocessor stage, so you can have file-local directives.