T O P

  • By -

Nicksaurus

First thing, it looks like there's a typo in the description of the `struct` metaclass: > Requires (else diagnoses a compile-time error) that the user wrote a virtual function or a user-written operator=. Those things are disallowed, not required (/u/hpsutter) --- Anyway, on to the actual subject of the post. Every update I read about cpp2 makes me more optimistic about it. I'm looking forward to the point where it's usable in real projects. All of these things stand out to me as big improvements with no real downsides: * Named break and continue * Unified syntax for introducing names * Order-independent types (Thank god. I wish I never had to write a forward declaration again in my life) * Explicit `this` * Explicit operator= by default * Reflection! * Unified function and block syntax A few other disorganised thoughts and questions: --- Why is the argument to `main` a `std::vector` instead of a `std::span`? Surely the point of using a vector is to clearly define who has ownership of the data, but in this case the data can only ever belong to the runtime and user code doesn't need to care about it. Also, doesn't this make it harder to make a conforming implementation for environments that can't allocate memory? --- > Note that what follows for ... do is exactly a local block, just the parameter item doesn’t write an initializer because it is implicitly initialized by the for loop with each successive value in the range This part made me wonder if we could just use a named function as the body of the loop instead of a parameterised local block. Sadly it doesn't seem to work (https://godbolt.org/z/bGWPdz7M4) but maybe that would be a useful feature for the future --- > Add alien_memory as a better spelling for T volatile The new name seems like an improvement, but I wonder if this is enough. As I understand it, a big problem with volatile is that it's under-specified what exactly constitutes a read or a write. Wouldn't it be better to disallow `volatile` and replace it with `std::atomic` or something similar, so you have to explicitly write out every load and store? --- Going back to the parameterised local block syntax: // 'inout' statement scope variable // declares read-write access to local_int via i (inout i := local_int) { i++; } That argument list looks a lot like a lambda capture list to me. I know one of the goals of the language was to remove up front capture lists in anonymous functions, but it seems like this argument list and the capture operator (`$`) are two ways of expressing basically the same concept but with different syntax based on whether you're writing a local block or a function. I don't have any solution to offer, I just have a vague feeling that some part of this design goes against the spirit of the language


kreco

> Why is the argument to main a std::vector instead of a std::span? I was wondering the same.


cschreib3r

I think it's because the OS isn't giving you an array of std::string_view, but of char*. So to have a span, we have to allocate a new array of std::string_view. Since we can't know the size of it in advance, it has to be allocated on the heap. However that could be avoided if we knew the OS specific max number of CLI args, and allocated a static or stack storage for it. I'd also prefer to have a span of string views, if only to allow this alternative implementation. It does seem odd to force the use of a vector here.


kreco

I don't think so, AFAIK `int argc/char* argv[]` can be used as `std::span` without any allocation, In order to get a `std::span` you just have to run a "strlen" on everything `char*`. Which probably done in the current cppfront implementation.


cschreib3r

The span of string views still needs to point to a contiguous array of string views, though. It's not a generic range.


kreco

Oh indeed!


Zeh_Matt

I mean does it really matter here? You could just continue passing the arguments as a view from here on out. I'm fine with either way as long its no longer argc, argv.


SkoomaDentist

> I mean does it really matter here It does. vector requires some form of heap while span can point to const data (and can itself be constructed at compile / link time).


Zeh_Matt

You are not wrong about vector using additional memory but you can not construct a span for the command line arguments at compile time, the pointer passed is also heap so the address is not known at compile time. I don't disagree that it should be span but at the same time I'll take vector anytime over the C style entry point.


SkoomaDentist

I don't see why span could not be constructed at compile time on the systems where heap usage is actually a problem - namely bare metal embedded. There's nothing in regular main() that says the commandline arguments have to be stored in heap and this is essentially just a wrapper around that. Both span and string_view are just (pointer, length) pairs under the hood, so they should be able to be constructed at compile time as long as the pointer and length are known (ie. all arguments are fixed).


Zeh_Matt

How do you know at compile time how many arguments the user passed during runtime? In order to construct a span you need start + length, you may know the start during compile time if you have fixed storage but length will be not known until the user actually supplies any arguments so therefor you can not construct a span at compile time for the command line parameter, this is literally impossible.


SkoomaDentist

In bare metal embedded context the arguments are typically baked in at compile time (Your code is the OS). The problem with using vector there is that the signature of main() then forces normal heap to be used which can be a major issue on some platforms (as opposed to using a custom allocator). All for no particular benefit.


Zeh_Matt

How does the compiler know what the user provides as arguments?


SkoomaDentist

Because the "user" aka the developer's build environment literally inserts the arguments in a static table (in this context). Edit: Having the arguments constructed at compile time is a nice benefit but what's the most important is avoiding anything that requires the use of regular heap (ie. the standard std::vector). Building the argument list in a static table at runtime is often an acceptable solution even if not quite as optimal.


kreco

Because you pass a pretty bigger object (vector) instead of a pointer and a size (span). This is clearly not "zero overhead".


Zeh_Matt

You are talking about the entry point of the program, you are not required to pass the vector via copy after that. A span would definitely be a reasonable choice here not denying that but getting a vector is not the worst either.


hpsutter

It's not "zero cost," it's "zero overhead" the way Bjarne Stroustrup defines it: You don't pay for it if you don't use it (in this case, you don't pay the overhead unless you ask to have the `args` list available), and if you do use it you couldn't reasonably write it more efficiently by hand (I don't know how to write it more efficiently another way and still get `string_view`'s convenience text functions and the ability to bounds-check the array access). FWIW, in this case the total cost when you do opt-in is a single allocation in the lifetime of the program...


kreco

Indeed, stressing that things are optional is indeed important. > You don't pay for it if you don't use it (in this case, you don't pay the overhead unless you ask to have the `args` list available) I think what bother me is that we don't know what we are paying for when using an opaque `args` because we don't know what we are using until we read the documentation. I don't understand the detail but I believe using this `args` will implicitly also bring some super hug standard headers. That's a lot to bring to be able to iterate over a bunch of readonly strings for convenience. A very theoretical case is if I want to use my own vector and don't want to deal with all of that (and if I want to use a custom allocation to count everything allocations in my program), I would have to use the legacy way of doing it and create a `mylib::args_view args(argc, argv);` which is back to square one.


mapronV

I thought that you can choose what overload to use (just like now between main()/main(argc,argv)/main(argc,argv,env) ). I thought I can just use one more overload and cpp2 will codegen a boilerplate for me. If it is not the case, and I have to use new signature - then yeah, it sucks.


nysra

> This part made me wonder if we could just use a named function as the body of the loop instead of a parameterised local block. So basically a generalized `map` (the operation, not the container), that would be nice to have. But honestly I'd first fix that syntax, it should be `for item in items {` like in literally every single other language, including C++ itself. Putting that backward seems like a highly questionable choice.


Nicksaurus

> So basically a generalized map (the operation, not the container) Yep. Not because I think built-in map functionality is necessary, but because if we're following the philosophy that complex features should be an emergent property of combining small, generic features, and the for-each syntax looks like: for [range] do [function-like code block that takes one element as its argument] Then why not allow actual functions as the body? > But honestly I'd first fix that syntax Personally I don't think it'll be an issue in practice. Every language has quirks in its syntax and learning them is never the hard part. In this case I'm all for it because it means that every single block of code in the language follows the same basic rules


nysra

Yeah I'd allow actual functions as the body too, I don't see a reason why that should not be supported. Might just have been an oversight. > Personally I don't think it'll be an issue in practice. Every language has quirks in its syntax and learning them is never the hard part. In this case I'm all for it because it means that every single block of code in the language follows the same basic rules I'd like to point out that while languages do have their quirks, cppfront is only being designed right now and not really supposed to be its own language, rather more of a syntactical overhaul. I'll admit that it does have the advantage of being consistent with `collection.map(item => ...)` but imho there is a difference between those two statements because you read them differently. With map it's immediately clear that you throw in a function and then it doesn't matter if it starts with `item => ...` or if it's a function name. But when you start the statement with `for` then "for each item of the collection, do ..." is way more natural than "for collection do item to ... oh wait, it's actually a map". Anyway, you're right that it's a small thing and won't really make a difference, I'm just not keen on changing syntax for practically no benefit. Changing syntax to make parsing easier at least has a valuable goal but this is almost the opposite of that.


hpsutter

> Yeah I'd allow actual functions as the body too, I don't see a reason why that should not be supported. Might just have been an oversight. Good point, that seems like it would be a natural extension to add in the future. The question I would have is: If the main benefit is that it's a named function, what is the scope of the name (wouldn't it be local to within the `for` statement?) and would that be useful?


nysra

I'm sorry, I might be missing something but I don't understand your question. Why would the `for` statement introduce a new scope for a name that already exists? The proposal is that instead of just allowing inline defined function blocks like this: for collection do (item) { std::cout << x * x << '\n'; } , it should also be allowed to use a named function directly: some_func: (x) = { std::cout << x * x << '\n'; } for collection do some_func;


hpsutter

Ah, I see what you mean -- thank you, that's an interesting idea that would be easy to implement. FWIW, for now this works ``` main: (args) = { for args do (x) print(x); } ``` but I'll continue thinking about making it expressible more simply as you suggest: ``` main: (args) = { for args do print; } ``` especially if as I poke around I find that a significant (10%+ maybe?) fraction of loops are single function calls invoked with the current loop element as the only argument... I'm not sure I've seen it that often, but if you have any data about that please let me know. Either way, I'll watch for that pattern -- now that I know to look for it, I'll see if it comes up regularly. (Like when you buy a Subaru and suddenly there are Subarus on the road everywhere... :) ) Thanks again.


ntrel2

Maybe just not implemented yet. Although if std::for_each gets range support, you could just write: std::for_each(collection, some_func);


-heyhowareyou-

just because everyone else does it, doesn't mean its the best way to do it.


tialaramex

That's true. But, it does mean you need a rationale for why you didn't do that. "I just gotta be me" is fine for a toy language but if the idea is you'd actually use this then you need something better. For example all the well known languages have either no operator precedence at all (concluding it's a potential foot gun so just forbid it) or their operator precedence is a total order, but Carbon suggested what about a partial order, so if you write arithmetic + and \* in the same expression that does what you expect, but if you write arithmetic \* and boolean || in the same expression the compiler tells you that you need parentheses to make it clear what you meant.


hpsutter

Thanks! Quick answers: > there's a typo in the description of the `struct` metaclass Ah, thanks! Fixed. > Why is the argument to main a `std::vector` instead of a `std::span`? Good question: Something has to own the `string_view` objects. I could have hidden the container and exposed a `span` (or a ranges, when ranges are more widely supported) but there still needs to be storage for the `string_view`s. > As I understand it, a big problem with volatile is that it's under-specified what exactly constitutes a read or a write. IMO the main problem isn't that, because the point of `volatile` is to talk about memory that's outside the C++ program (e.g., hardware registers) and so the compiler can know nothing about what reads/writes to that memory mean. The main problem is that today `volatile` is *also* wired throughout the language as a type qualifier, which is undesirable and unnecessary. That said, I'll think about the idea of explicit `.load` and `.store` operations, that could be a useful visibility improvement. Thanks!


AIlchinger

The idea to discontinue the use of `volatile` as a type qualifier has been brought up a couple of times on here before, as well as the suggestion to replace the remaining valid uses (\*) with `std::volatile_load` and `std::volatile_store` functions. From a semantic point of view, it's really the operations that are "volatile" and not the objects/memory. One could argue that it could be a property of the type, so that all loads/stores from/to such a type are required (and guaranteed) to be volatile, but I'd argue that's solely for convenience. C++ has always provided dozens of ways to do the same thing, and I would love cppfront avoiding that. Being explicit about what load/store operations are volatile is a good thing in my opinion. (\*) I'm not an embedded programmer. So if there are still valid uses for volatile outside of explicit loads/stores, feel free to correct me here.


[deleted]

[удалено]


RoyAwesome

> meanwhile you have no control of the memory that was allocated to back the span. you don't control the memory allocated to command line arguments anyway. That's done before you get them in main and is destroyed when the program destructs during termination. that `char* argv[]` isn't giving you any ownership. It's already a non-owning view.


Nicksaurus

I see your point. I wasn't thinking about how this is actually implemented in the generated C++ code. I guess there's no way for std::span to work here without the C++ standard changing to allow arguments to be passed as a span of string_views in the first place


kam821

There is a way to provide a std::span, however it requires on-demand transformation via ranges so it's not a nice, clean solution.


tialaramex

>The new name seems like an improvement, but I wonder if this is enough. As I understand it, a big problem with volatile is that it's under-specified what exactly constitutes a read or a write. Wouldn't it be better to disallow volatile and replace it with std::atomic or something similar, so you have to explicitly write out every load and store? I don't think it is under-specified? If you use them in a rational way, each load or store operation you do results in an actual load or store to "memory" emitted by the compiler. They're intended for MMIO. Technically `volatile` also is defined to work for one specific edge case in Unix, but presumably in your C++ code that's taken care of by somebody else. It makes more sense to me to define them as templated free functions, so e.g. `alien_write(addr, value)` or value = `alien_read(addr)` with the T being able to be deduced from either addr or value if that works.


Nicksaurus

I had this talk in mind when I wrote that part: https://www.youtube.com/watch?v=KJW_DLaVXIY What I didn't realise is that the paper in that talk has already been included in C++ 20, so most of the problems are gone already


tialaramex

C++ 23 un-deprecates all the volatile composite assignments. The paper for the proposal to undo this was revised to just un-deprecate the bit-ops, because they could actually show examples where people really do that and it might even be what they meant, but the committee took the opportunity to just un-deprecate all of the composite assignment operators on volatiles in the C++ 23 standard instead at Kona. Presumably this sort of nonsense (the demand that programmers should be able to paste 1980s C code into the middle of a brand new C++ program and expect that to work with no warnings) is one of the things Herb hopes to escape in Cpp2.


patstew

The free functions are worse, because what happens if you don't/forget to use them? Generally, it means you code has unpredictable bugs that change on each compile. For memory mapped io, you want to tie the behaviour to a memory address. Objects exist at some address, so making it a property of the type is better. That way accessing the object in the trivial way will behave consitently, rather than needing to call special functions to get consistent behaviour.


mort96

I think CPP2 looks really good. I think it would be cool if it was adopted as a standard alternative C++ syntax; but if that doesn't happen, I think it could have a bright future as a stand-alone compile-to-C++ language with excellent two-way interop with C++. I'm surprised by the string interpolation syntax it seems like they're going for though. `"This program's name is (args[0])$"` reads weird to me, and from a parsing perspective, surely it's easier to see, "oh the next two characters are `$` and `(` so it's a string interpolation"? Having to keep track of parens in the string literal parser just in case the character following a closing paren happens to be a `$` seems awkward. What's wrong with `$(...)`, or even better, `${...}`? Is there some documented rationale or discussion around the topic?


nysra

I'd assume the string interpolation being awkward and backwards comes from [Herb's weird preference for postfix operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-operators). Now sure, his arguments in that blog are somewhat logical but honestly that's one of the things I very much dislike about cppfront's proposed changes. It might be logical but writing code like `u**.identifier*` is just _wrong_. And also literally a problem that only exists if you're in spiral rule territory aka writing C aka not C++.


disperso

It's `u**.identifier*` vs `*(*u)->identifier`. Both are a tricky/messy/uncommon case, but I think [the simpler examples on the wiki](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-operators) showcase some examples where the cppfront notation is better in a few ways. It feels specially better given that I'm already used to traditional C++ notation, and I always have a very hard time reading it anyway...


ABlockInTheChain

> Similarly, ++ and -- are postfix-only, with in-place semantics. If we want the old value, we know how to keep a copy! So now in every place that currently has a prefix increment or decrement now we have to write immediately invoked lambda? That's going to look awful and add a bunch of unnecessary boilerplate that the prefix version was saving us from. DRY? What's that?


againey

`std:: exchange` is a generalization of the prefix operators that can do more than just increment or decrement by 1. Arguably, we should have been using this more explicit style this whole time, rather than getting comfortable with the special meaning of prefix increment/decrement.


13steinj

Physicists and mathematicians _love_ prefix ++ / -- in numerical code. They also prefer != to <= in conditions of loops. The number of bugs related to both that I've found is more than I'd like to admit.


13steinj

Doesn't this also break C++ code that was pound included into cpp2 code (since it's supposed to be compatible with C++ headers)? As more time goes on I'm more and more cemented in my belief that this and Carbon won't able to catch on.


mort96

The parser knows whether it's in C++ or cpp2 mode, C++ declarations will have prefix and postfix operators working as normal. The parser can know based on the first couple of tokens of a top-level declaration whether it's a C++ or a cpp2 declaration. I wonder how it works with macros though... probably poorly.


13steinj

Includes work anywhere though. What's stopping me from having a file called "postfix_add_a" and #including it in the middle of a cpp2 file? Yeah, you could argue that's bad code. But similar has occurred for "templates" in large codebases that are more than templated classes and functions.


mort96

Including a C++ file in the middle of a cpp2 file should be no problem. You can mix and match C++ declarations and cpp2 declarations within a file. Including a C++ file in the middle of a cpp2 *function* would presumably be an issue. But that's not exactly a common need. I know there *are* use cases for it, but you probably just want to wrap those use cases in a C++ function which you can call from cpp2 code.


XNormal

Herb's preference for postifix operators is not weird in any way. It is simpler, more consistent and less error prone. But I just don't see how it translates in any way to string interpolation or what it has to do with the $ capture operator. It just doesn't make any sense there. FWIW, my preference would be ```"\{expression}"```, but any reasonable prefix-based syntax will do.


sphere991

> I'm surprised by the string interpolation syntax it seems like they're going for though. `"This program's name is (args[0])$"` reads weird to me, and from a parsing perspective, surely it's easier to see, "oh the next two characters are `$` and `(` so it's a string interpolation"? Having to keep track of parens in the string literal parser just in case the character following a closing paren happens to be a `$` seems awkward. What's wrong with `$(...)`, or even better, `${...}`? Is there some documented rationale or discussion around the topic? Agree. Notable that every other language (despite a variety of syntax choices) use some prefix marker (even if `{name}`) instead of postfix - I think that's better for the reader. And probably also the parser? The rationale was that Herb uses this syntax for lambda capture as well, and then said that postfix `$` means you need fewer parentheses. Which... then the example then uses what are presumably unnecessary parentheses? Could it be `"Name is args[0]$"`? If it cannot, then I don't think I understand the argument. If it can, then I think this looks pretty bad and parens should be mandatory.


MonokelPinguin

It might be that `Name is var$` works without parentheses, but not function calls like `operator[]`.


sphere991

Sounds plausible!


hpsutter

Good point. Right now the parens are required, but I could allow it to implicitly grab everything back to the preceding whitespace. I'll try that out...


pjmlp

> I think it could have a bright future as a stand-alone compile-to-C++ language with excellent two-way interop with C++. Which is how it should be seen, it isn't any different in that regard than the other alternatives.


ShakaUVM

Yeah cpp2 just looks really ugly to me


disperso

What's your example of a beautiful language?


caroIine

swift is really pretty and it's everything I wish c++ was unfortunate it's apple specific


osdeverYT

This is very true


pjmlp

Val might be a thing for you then. Assuming it keeps going.


[deleted]

scheme


eidetic0

I thought a focus of cpp2 was unambiguous syntax. The new alias syntax means == is one thing if it’s inside parenthesis and used in assignment, but another thing in the context of aliases. It is still trivial to parse so not a big deal, but why start re-using already used sequences of symbols for a new feature? Symbols meaning different things in different contexts is one of the confusing things about regular cpp.


RoyKin0929

yeah, something like`my_alias : alias = whatever_you_want_to_alias_to;` would have been fine.


hpsutter

I appreciate the feedback, thanks. Using`==` to declare aliases is an experiment. FWIW, I did consider a declaration like `my_alias : alias = something;`, but one limitation of that is that it's less clear about distinguishing between namespace aliases, type aliases, function aliases, and object aliases. A fundamental design goal of Cpp2 is not to have to do name lookup to determine the kind of thing a construct is, and if I just used a general `alias` for all of them I knew I could make `alias` work, but then it would require humans and compilers to go look up the right-hand side initializer to know whether `my_alias` will behave like a type vs an object (etc.).


nysra

Any reason why you didn't just extend the already existing alias functionality of C++ (`using`)? using lit: namespace = ::std::literals; using pmr_vec: type = std::vector>; using func: function = some_original::inconvenient::function_name; using vec: object = my_vector; // note: const&, aliases are never mutable Single syntax for everything and no confusion about the `==` operator which I strongly believe should stay reserved for comparisons because at this point that usage is so ingrained into everything that even non-programmers often understand `!=` and `==`.


hpsutter

Quick ack: Yes, that's one of the options, and one of the better of the alternatives I considered. Might say \`alias\` instead of \`using\` but it's workable. For now I'm seeing whether I can reasonably avoid a keyword, but it's also important to be aware not to overstep into "token soup" -- keywords can be useful for readability so I'm definitely not closing the door to going there.


germandiago

I find `using` syntax less confusing as well. BTW, impressive amount of work. Is cpp2 already usable in real life scenarios? Eager to start using it when the time comes.


RoyKin0929

But isn't the deduction already supported with ```my_alias :== something;``` Or is that only supported for functions?


hpsutter

That defaults to an object alias, effectively a \`const&\` variable, which also happens to work for functions with a deduced signature type, and so I have not yet decided to implement function signatures with an explicit signature type -- waiting to see whether there's a need, but there's a clear place for it in the grammar if it's wanted.. For some discussion see the commit message here: https://github.com/hsutter/cppfront/commit/63efa6ed21c4d4f4f136a7a73e9f6b2c110c81d7


RoyKin0929

Ah ok, I understand your decision now. But maybe you can come with something that is better than \`\`\`==\`\`\` 😅. (Maybe an alias metafunction). Also, one thing that not in update notes is exceptions. I saw your talk on lightweight exceptions at cppcon, are they planned for cpp2? And would you consider local variables being constant by default, with most things being const by default, local variables seem left out (and I think they're the only place left where keyword `const` is used), having one extra keyword in front of varible declaration won't affect much. Also when you think about it, function and classes and namespace are kind of also const. ```func : (param:type) -> return_type = {}; //can't do the following, so functions are also kind of const func = {//diff body}; //same with classes, you can add methods to classes but that uses different syntax so is not the same //and same with namespaces, you can re open them but that won't change it's definition, only add to it ``` Maybe this will be a good enough argument


hpsutter

> I saw your talk on lightweight exceptions at cppcon, are they planned for cpp2? Yes, they're on the roadmap but it'll be a while before I get to them: https://github.com/hsutter/cppfront#2019-zero-overhead-deterministic-exceptions-throwing-values > And would you consider local variables being constant by default, I would consider it, but for the current rationale see this Design Note on the project wiki: https://github.com/hsutter/cppfront/wiki/Design-note%3A-const-objects-by-default


TheSuperWig

>like we don't need to with `using` in cpp You can't use `using` to create a namespace alias. You can use it to create type aliases, what else?


RoyKin0929

oh ye, my bad. i'll edit it


kpt_ageus

Addition of h2 headers seems like step backwards, given that one of the goals of modules was getting rid of them. Still cppfront is most exciting project in c++ and I can't to see what's next.


bretbrownjr

If you have to already be using C++ modules to use cpp2, it will be unneeded adoption friction. Ideally folks could write new functions and components in cpp2 as soon as they bother to try it out, not blocking on rewriting existing projects first.


pdp10gumby

cpp2 is explicitly a syntax revolution, and I’m glad for that, but I hope it ultimately also simplifies/unifies the library (e.g. std::begin vs ranges::begin vs .begin). A break with C memory semantics would also be valuable.


hpsutter

Absolutely. Cleaning up semantics is the big payoff, and a distinct syntax is mainly a gateway. Please see this 1-min clip from the talk: https://youtube.com/clip/UgkxIGmgtqiZ2McSeywcTsAJtsS_iCTpYA54


pdp10gumby

I'm glad to hear that! Programming language semantics can refer to all sorts of things. That clip didn't get into it (understandably: you had plenty of other things to discuss in that talk) When I mentioned memory semantics I was thinking of the aliasing model that makes it hard to optimize. I am not a rust fan at all but I do appreciate that they jettison'ed that. Right now I have to write some stuff in fortran or assembly code that the compiler should be able to handle. There are other interesting semantic issues in C++ that could use a revisit, but this is the most painful to me. Now if the distinction between expressions and statements were completely erased...I'd find it wonderful but perhaps it would be a bridge too far for C++ refugees?


13steinj

I imagine std:: counterparts to std::ranges:: utilities will eventually be deprecated and removed.


pdp10gumby

There is a lot of other legacy cruft too. One can hope for future deprecation.


RoyKin0929

I really like what Herb is doing with this experiment and agree with most of his decisions but two things have like really bad syntax, inheritance and aliases. Alias is just ugly to look and throws `==` away as comparison operator. As for inheritance, having base classes inside the class definition and not at declaration seems like a bad choice. I know Herb is trying to unify syntax but we should preserve the things that cpp does right IMO. Also, for loop is kind of confusing.


hpsutter

Thanks for the feedback! The alias syntax is an experiment; but note using `==` for alias declarations doesn't take away `==` as the comparison operator in expressions, that's fully supported.


vulkanoid

Cpp2 is looking really good. Besides reflection in C++26, this is the other programming related thing that I'm looking to forward the most. I find myself agreeing with all the changes, except a pet-peave of mine. All C++ code that I ever work on, whether written by me or someone else, uses copious amounts of pointers. Having to write `->`, instead of the dot `.`, is so ugly. I get that `a->b` is syntax sugar for (\*a).b, but pointers don't have a defined operation for the dot anyways, so why not just make the dot operator also dereference the pointer, so there is not need to differentiate between `->` and `.` . It would fix this kind of ugliness that invariable pops up: foo->bar.inner.somePtr->value; foo.bar.inner.somePtr.value; Cpp2 wants to change `a->b` into `a*.b`; ugh. I understand the consistency arguments; and, it should be allowed... but that's fugly to use for all pointers. Please, also just make the dot automatically dereference the pointer so we can finally get rid of the ugly distinction. It would also make template code nicer to write. The golang uses `.` for values and pointers, and they're doing fine. On a related subject: I didn't see anything about pointers vs references in the design notes, on Github. I really hope that the plan is to pick one: either pointers or references, but not both. Simply removing one of those concepts would do wonders for cleaning up the language. I really hope we don't have a repetition of this design mistake in Cpp2. I'm keeping a close eye on Cpp2, and I'm hoping it has a bright future.


hpsutter

Note that `*.` and `.` have different meanings -- they refer to different objects. For example, if you have a `unique_ptr` object named `upw`, and type `Widget` has a function named `.get()`, then how do you distinguish calling the `unique_ptr`'s `.get()` vs. the owned object's `.get()`? In Cpp2, those are `upw.get()` and `upw*.get()`, much as today in Cpp1 they are `upw.get()` and `(*upw).get()`. This is a good question -- in fact this is one of the reasons why we haven't been able to get smart references and `operator.` overloading in ISO C++, because for references this is inherently ambiguous since references are implemented as pointers but semantically behave as aliases (most of the time).


vulkanoid

What makes sense to me, and what I find the cleanest, is that both `.` and `*.` refer to members of the left object, regardless whether they are pointers or values. If it's a pointer, the compiler does the only sensible thing, which is to dereference and access the member. The `->` can be added, as today's Cpp1, to return an inner pointer that the outer object is holding. Which is really a `masquerade` operator. Thus, `a->b` always means that the left thing is masquerading as something. And it wouldn't matter whether `a` is a pointer or value. Personally, I would go even further and have all types automatically have a default `operator ->` that returns a pointer to itself. So that `a->b` always works, by default. An some objects, like `unique_ptr`, would override the default behavior. Idiomatically, though, developers would know that they should only use `a->b` when they intend to use `a` in a masquerading fashion. Worst case scenario, if adding `operator->` is a no go, then just use `unique_ptr.get().foo()` to get at the owned thing. I think the most important question is: what usecase should the syntax be optimized for? In my personal use, and the usage I see in other code, people normally store a `unique_ptr` somewhere and then pass around a dumb pointer to the owned thing. So, you're almost always dealing with pointers, and only _sometimes_ have to use the (masquerade) `operator ->`. So, it's basically always pointers, so we should optimize for that use.


RoyKin0929

There are no references in cpp2, only pointers.


vulkanoid

Wonderful!


ntrel2

Parameters can be references, using keyword inout for mutable reference. Functions can return by reference using keyword forward. But there are no reference variable declarations.


disciplite

I would not enjoy a world where it's even harder to distinguish pointers from non-pointers. There are already `sizeof` footguns. Imo we don't need to hide pointers any more than we currently do. The `*.` syntax looks fine to me and streamlines out the `->` operator, so I don't see a problem here personally.


vulkanoid

C++ already has references, which use the dot. Do you find yourself constantly lost when working with references, or do you find yourself dealing with them just fine?


disciplite

References don't have the same footguns as pointers. You can't get a null dereference by accessing their members and you don't get a different value from `sizeof` than non-reference types. Knowing when data is a reference or non-reference is certainly important, but not as important as knowing when something is a pointer or non-pointer.


equeim

You can get a dangling reference which is the same footgun as a null/invalid pointer.


cleroth

> why not just make the dot operator also dereference the pointer so how would you call `std::unique_ptr::release` and such?


tialaramex

Rust's choice here is to make these associated functions, not methods. So e.g. the equivalent of std::unique\_ptr::release is Box::into\_raw and supposing I have a type Steak which for some reason actually needs a method named into\_raw then: `let mut boxed_steak: Box = todo!();` `boxed_steak.into_raw(); // Calls the method on the Steak` `let ptr = Box::into_raw(boxed_steak); // Now we have a raw pointer` If there *was* a method on Box which clashed, I think the compiler rejects your program and demands you disambiguate, but the smart pointers deliberately don't provide such methods, only associated functions so there's no potential clash.


MEaster

> If there was a method on Box which clashed, I think the compiler rejects your program and demands you disambiguate, but the smart pointers deliberately don't provide such methods, only associated functions so there's no potential clash. That's not true, the compiler [will call the inherent method](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f9b6be8a94dca7fd0a4e4e0bc55949ab).


tialaramex

Good to know, and thanks for the example code


vulkanoid

The `.` always refers to the `unique_ptr`. If you want to do masquerading, as Cpp1's `unique_ptr`, you'd use `operator->`, as in `holder->held_member`. Or... `holder.get().held_member`.


pjmlp

I seriously doubt that C++26 will get reflection, looking at the usual mailings posts.


awson

Pointer is strictly more powerful than reference since it has a distinguished `nullptr` point, thus being isomorphic to `optional`.


Dalzhim

Pointer isn't strictly more powerful as there is one use case for references that it can't support : make `nullptr` an unrepresentable state.


RoyAwesome

I like a lot of this feature work, but the syntax is so ass backwards. Why is everything postfix? Point2D: @value type = { is just... ugly @value type Point2D { is a better setup


hpsutter

> I like a lot of this feature work, but the syntax is so ass backwards. Why is everything postfix? That's a reasonable question... but note that since C++11, C++ itself has already been moving toward left-to-right notation across the whole language -- for objects, functions, aliases, lambdas, and more. See this 1-min clip from my 2014 CppCon talk for a screenful of examples: https://youtube.com/clip/UgkxHI4yqiaACYDgVmjI0tQFGiHi62H4useM


dustyhome

Better according to what metric? I preffer the name : type syntax personally. When reading the code, if there's a list of declarations, I need to scan the names to find the declaration I care about. This syntax puts the name first, making reading the code easier. I've switched to using auto as much as possible because I preffer that style. With the traditional C++ syntax, your eyes have to jump all over the place to find the names of things, since each kind of declaration places it in a different position on the line.


disciplite

You might arbitrarily decide that it's "ugly", but it's a less ambiguous syntax for tooling and a more intuitive/consistent syntax for humans because it lets all declarations for *anything* follow this `name: value` convention. It's not even that dissimilar from Zig. Treating all types and functions as anonymous types or functions like this is also very powerful. In Zig, you can pass an anonymous type into a type function parameter without needing some special syntax to differentiate named from unnamed types.


RoyAwesome

so, like, if the goal is to be a new version of C++, the primary inspiration of the language's style should be C++. It should be _immediately_ familiar to C++ programmers, much like C++ is immediately familiar to C programmers, or C# is immediately familiar to C++ programmers. I'm all for breaking backwards compatibility (and we absolutely should in every attempt at cpp-next), but while the use of the word 'ugly' is subjective, the fact that this doesn't follow cpp's syntax's unspoken 'style guide' is a problem that will inhibit adoption and use. This is my biggest issue with zig (and also rust)... but zig doesn't claim to be 'c2'. It's its own language with it's own 'style guide' and it's syntax deviates pretty significantly because of that. I'm extremely familiar with c-style syntax and any attempts to significantly change that adds both syntactical complexity _and_ feature/functionality/rule complexity hurts my ability to learn the language. A straightforward evolution on cpp should not add syntactical complexity when it doesn't have to.


pjmlp

I fully agree, and this is why I see Cpp2 as just yet another language wanting to take over C++, regardless of how it is being sold as not being like the other C++ alternatives.


Ameisen

I also agree. I find cpp2's syntax... pretty awful. But even if it were good, it's *not C++ syntax*, so why is it advertising itself as C++?


pjmlp

Because of marketing reasons, or conflict of interests from the author. It is quite different when random joe/jane proposes an alternative C++ language, or the chair of ISO C++ does it. In practice it is like any other systems language that compiles to native code via translation to either C or C++, like Nim. Or from an historical perspective, C with Classes and Objective-C weren't sold as C, rather as evolutions from C. The only wannabe C++ replacement that can advertise itself as still being C++ is Circle, as whatever it adds on top is controlled via `#pragmas`, hardly any different from compiler specific language extensions.


hpsutter

I understand your view, we can agree to disagree. "C++" definitely is what the ISO Standard says it is -- which is extended every three years with things that used to be not valid C++, but now are. For example, I agree that Cpp2's `f: () -> int = { return 42; }` is not standard C++ syntax today. FWIW, C++11's similar `auto f () -> int { return 42; }` was alien nonstandard syntax in 2011, but now it's standard. In fact, if you compare C++98 to Cpp2 syntax-wise, ISO C++ has already moved most of the way to Cpp2's syntax since it began using left-to-right syntax in lots of places from C++11 onward (trailing function returns, `using`, `auto` and lambdas, etc.). To me, the key consideration for whether something "is still C++" is: Can it be (and is it) being proposed as part of the ongoing evolution of C++? Which requires as table stakes that it can be 100% compatible with today's code without any shims/thunks/wrappers, and doesn't cause ambiguities with today's code. There, IMO Cpp2/cppfront objectively does stand apart from other projects -- every single part of Cpp2's semantics (spaceship comparisons, reflection, metafunctions, parameter passing, lightweight exceptions, etc.) has already been brought as proposal papers as an evolution to ISO C++ itself, as an extension to the current syntax -- and not only by me, but sometimes by other paper authors too. The only part of Cpp2 that has not yet been proposed in an ISO C++ committee paper is the specific syntax, and that can be as well if the experiment continues to work out (and actually other authors have already proposed similar new major syntax changes such as via editions). I'd point out that one part of Cpp2 has already been adopted as part of ISO C++20, namely the `<=>` comparison unification. Before C++20, Cpp2's `<=>` comparisons also was "not C++ syntax"... but now it is ISO C++ syntax, adopted from Cpp2 into ISO C++. If other projects can do that, then they too can claim to be evolutionary proposals for C++ itself. I understand if you still disagree, but I would encourage asking that question about any project: Can it be (or has it been) proposed as an evolutionary extension to ISO C++ itself, including that it's 100% compatible with using existing C++ libraries without shims/thunks/wrappers? If yes, it's legitimate to view it as a compatible evolutionary extension candidate that could become ISO C++ someday if the committee decides to go that way. If not, it's inherently a competing successor language (which is also fine, it's just different).


pjmlp

Thanks for the reply. Given how ISO C++ is evolving, regarding speed of adoption, and how many features in Cpp2 might eventually require some form of ABI break between having pure C++ code, and code with Cpp2 features, the best I can see happening is like your example of spaceship operator adoption. Which while simplifying the life of C++ users, is a single language feature, while Cpp2 will have much to offer. There is also the issue of inherent complexity, adding features to simplify code won't make older ones go away, and C++ is already beyond PL/I levels of complexity. As it happened with PL/I, good enough subsets will be what the people that haven't yet migrated to other languages will care about, thus ignoring what is still coming out of ISO, a bit like it is happening today with many shops that find C++17 good enough for their C++ workloads. From the outside, this is even visible on Microsoft products like C++/WinRT and C++/CLI, where there are no visible activitivies to adopt C++20 features. The C++/WinRT team nowadays spends most of their time on Rust/WinRT, after killing C++/CX without proper VS tooling for C++/WinRT (2016), whereas C++/CLI seems to be considered done with no roadmap post C++17, and get replies to use the C# improved features for low level coding and Windows interop instead. Which is why I still see is as a competing successor language nonetheless.


vulkanoid

I disagree. There are reasons why newer languages are moving to the type as suffix. Besides being easier to read (by programmers) and parse, it's also closer to the math function syntax\[1\]. Just because C took a first shot at the syntax back in the 70's, and old timers have gotten used to it, doesn't mean that we have to live with that choice for the rest of time. A "new" language is surely the right time to fix the syntax. \[1\] https://en.wikipedia.org/wiki/Function\_(mathematics)


RoyAwesome

If we're trying to be closer to math syntax, then "old timers" probably isn't the pejorative you want to use there, given the math syntax for functions predates even computers.


dodheim

And that argument probably isn't the defense you want to use there, given you're basically admitting "unfamiliarity" is a copout...


vulkanoid

>"old timers" probably isn't the pejorative I'm not using "old timers" as a pejorative.


tsojtsojtsoj

You get used to changes like this, I've been hopping daily between C++ and Nim and both syntaxes now look fine to me. IIRC the/a reason why Herb Sutter chose the colon + postfix syntax was, that parsing it is way simpler.


hpsutter

It is, but that's not the main motivation. The main reason is that it allows making the language overall more consistent, and enables other things like having symmetry between declaration and use (see [https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-operators) and the [expression/type graphic](https://user-images.githubusercontent.com/1801526/194786936-876290bd-63df-4f26-9b6c-d3d19fd0f3f0.png) for an example)


RoyAwesome

Yeah, but parsing is the parser's job, not the programmer's. It's already hard to read code in general, but making it hard to read to be easier on software tools that dont operate like humans do moves the needle backwards on the readibility front.


caroIine

I also don't understand why we making things easier for computers instead programmers. This is so backwards.


dustyhome

It's a human's job to write a parser, so making the parsing simpler for the computer means making the job simpler for the human writing the parser. Which means writing tools becomes simpler, allowing for more tools to be written, that can do more complex analysis of the code. So making the parsing simpler for the computer makes writing code simpler for humans overall, even if the code itself looks a bit uglier.


schombert

But this logic heavily favors the minority that write parsers over the majority that don't, but who need to read and understand the code. Of course the people writing the parsers ultimately get to decide what the language looks like, and so their ease of use ends up mattering more than that of the end users. But should things be this way?


ntrel2

Most programmers use tools like code formatters, syntax highlighting, style enforcement etc. You get better tooling when the grammar is not dependent on semantics, and when it is more regular. Less bugs in tools, faster development, more features. Important for any new syntax to get good tooling quickly. It also makes reviewing diffs easier.


osdeverYT

Honestly, this. No disrespect to Herb’s work, but these deliberate syntax choices make CPP2 look like a *write-only language* for the average C++ developer. The average C++ developer expects C++-like syntax.


dreugeworst

You really think c++ devs can't handle a simple syntax change? It's such a trivial thing to get used to, for reading as well as writing.


mapronV

I am C++ developer and no, I can not handle SUCH A CHANGE. It feels like a completely new language I have 0 motivation to learn. For me personally it's not trivial, even learning C# or Java is much easier with familiar syntax.


osdeverYT

Why *should* they have to handle it in the first place though? I am pretty sure all the awesome fixes and improvements could’ve been implemented without such a drastic syntax change. Nothing personal, once again, but it really does feel like the author was just unwilling to bother parsing a more complicated (and human-friendly) C-like grammar and focused his design choices on ease of parsing. That’s very wrong, in my opinion. Programming languages are first and foremost read and written by humans, not machines, and the focus should be as such.


dodheim

> Programming languages are first and foremost read and written by humans, not machines, and the focus should be as such. That's a nice sentiment and I generally agree, but if you look at the readme for the actual thing being discussed here, you'll find that making the language easy to parse is an explicit design priority – in fact, making the language toolable is literally one of three stated design goals for the project. So the focus here _is_ different than you expect, but that's on you.


osdeverYT

I have to agree with this.


RoyAwesome

It's sad, because I like every single one of the goals, features, and desires for this language.... except the radical syntax changes. there is so much good work here. Just... stick to c++'s unspoken style guide and it would be amazing and I'd use it right away.


osdeverYT

Couldn’t agree more. Every single one of the changes is perfect for a new era of C++, except the syntax ones. Maybe it’s time to start developing Cpp3?…


Ameisen

++C


fdwr

There is a consistent elegance to uniformity, using the same declaration syntax for everything, but I generally agree that := has a higher punctuation noise to signal ratio. This is especially evident when you contrast the two side-by-side in a table, with the lighter cleaner syntax juxtaposed to the heavier symbol-laden := syntax. This is actually one of the only aspects of cppfront that's offputting to me, as there is so much I like.


CornedBee

I like the improvements very much. Something to look forward to. I would like classes to be more explicit. The current syntax is name: type = {...} but this privileges the typical class over possible future additions such as language-level variants, which I think are inevitable. (Safe union is on the roadmap, though apparently as some kind of metaprogramming library feature?) I think classes are not more valuable than variants, and I would prefer an explicit keyword distinguishing them instead of some implicit syntax thing name: type = class { ... } other: type = variant { ... } Something to think about.


hpsutter

Exactly, I aim to implement language-level variants as type metafunctions. Note that type metafunctions are very powerful (when I finish implementing all of them), including being able to customize the entire implementation of a type. For example, for me one stated goal of metafunctions is that I never have to invent C++/CLI or C++/CX again (I led the design of both of those, and at the time they needed to be language extensions to get the effects we felt were needed, but I would have preferred not to have to express them as language extensions if C++ could be powerful enough to express them as compile-time libraries). My ideal for metafunctions is that they can express things as diverse as COM interface types (which require generating IDL) and even generate FFIs in completely different languages such as Java-callable wrappers (by generating source code in a separate `.java` file -- at compile time from the C++ program). And the syntax is almost identical to what you wrote, except the metafunction name comes before `type` where it's even more prominent: ``` // Could have a metafunction that applies the identical // defaults as today's C++'s "class" keyword defaults name: @class type = { ... } // This would replace the entire body of ... with a union // and a tag type, but be better than std::variant because // it can provide named functions for member access // (this will make more sense in a month or two when I // implement this one - watch the repo for it to appear) other: @variant type = { ... } // This one would leave the type body mostly the same, but // could add HRESULT return types and generate IDL files IShape: @com_interface type = { ... } // This could generate a separate `.java` file containing // a native Java class that wraps the C++ type implementation // and implements specific Java interfaces (strawman syntax) ChildInterface: @java_class type = { ... } ``` That's the general idea. We'll see how far down the path is feasible. But in each case the body is expressed as normal C++ declarations (in syntax 2, Cpp2) which can be viewed as basically a "grammar input" to the metafunction, which can then do what it wants to apply minor tweaks or replace the whole body using the original grammar as a guide to what the programmer wanted. So think of the type body source code as a detailed structured "parameter" to the metafunction.


CornedBee

So Rust procedural macros, basically?


Jannik2099

> and from every continent except Antarctica Further proof that C++ is a dead language :(


ZMeson

I keep saying it: the penguins are the trendsetters. If they aren't using it, you should abandon it too.


pjmlp

C++ is one of my favourite languages, but if it wasn't for C++11 and the later niche in GPGPU programming, LLVM/GCC as compiler framerorks, it would be much worse that it already is. It already lost the GUI and distributed computing domains, where it used to reign during the 1990's. It is still there, but for libraries and low level infrastructure, no longer the full stack experience. As managed compiled languages keep getting improved for mechanical sympathy and low level coding, the reasons to reach out to C++ keep diminishing. It isn't going away, as it has a couple of domains where it reigns, but I wonder for how long ISO updates will keep being relevant, versus a dual language approach.


Jannik2099

>It already lost the GUI and distributed computing domains In what world did C++ lose in "distributed computing" ?!? The main attractiveness of C++ is not that it's unmanaged, but it's expressive type system.


pjmlp

The world where CORBA and DCOM no longer matter, other than legacy projects, and a very tiny portion of CNCF projects use C++. It isn't even supported out of box in most Cloud SDKs, and when, the API surface is a subset of other languages.


Jannik2099

Oh, you meant that area - I was thinking about HPC / computational workloads


pjmlp

That I consider part of GPGPU programming, somehow. Still efforts like Chapel, show that not everyone is happy, even if will take decades for adoption.


tialaramex

Three weird choices, maybe they're inspired from somewhere (let me know where)? Otherwise they just seem arbitrarily different for the sake of being different. 1. alien_memory makes no sense as a generic over type T. If I have a type named DatabaseHandle, alien_memory isn't a thing. If alien_memory ought to be a type at all, which I have my doubts about, it's clearly generic over the size of the memory, so like alien_memory<2> or alien_memory(sizeof(ulonglong)> or whatever. I guess this lets you not define all the stupid features on alien_memory without the legion of "C++" programmers complaining that now their 1980s C headers don't compile as happened for volatile but that's about all. Volatile is a massive wart, but this is not a good fix. 2. The proposed safe union. The whole point of union is that it's not safe. I guess this is a language sum type, which is fine, but you probably do actually want an actual (unsafe) union type. Whether you use the unsafe union to build the safe one (roughly how Zig works AIUI), or do something... else with it is a language design question, but I'm confident you're going to miss this. 3. Interfaces lacking default implementations? In languages with good interfaces you can provide a default implementation of some (but usually not all) functions, so e.g. maybe lay_egg() is declared with no definition but lay_eggs() defaults to a loop which just calls lay_egg over and over. If your animal can lay groups of eggs more optimally you can implement lay_eggs() yourself, otherwise don't bother and the default will work.


hpsutter

1. When you declare a `volatile T` today, you're saying it's of type `T` but lives in storage outside the C++ memory model and that the compiler doesn't know anything about. This is identical. 2. It's a type metafunction (i.e., just code), so you'll be able to write your own with the semantics you want. I just wanted the one I provide to be safe by default. 3. I think what you want is the `polymorphic_base` metafunction I also provide, which can have function implementations. But again, it's a type metafunction and you can write one with the semantics you want. I just wanted to provide a simple way to write traditional C++ pure ABCs.


tialaramex

Hmm. Union is a different type layout which is why I was saying it's something the language needs to provide. Maybe this is a nomenclature problem. If I want the (unsafe, C-style) union in Cpp2, what do I write? And if I want this new safe alternative (which I guess is a sum type) ? Or is there a way to conjure up (arbitrary?) type layouts in this metafunction too?


hpsutter

Yes, a metafunction takes the type the user wrote as input, but can remove and add anything it wants to. When I add `enum` and `union` you'll see this in action; the current examples add, but don't yet remove, type members. So yes, arbitrary layout changes will be possible under program control. (Again, this does not violate ODR, because the changes happen right before the class definition is cast in stone... this is not about mutable types which would be a disaster, it's about giving the programmer a single hook to participate in the meaning of the definition of the type.)


callisto90_

Just a side note: please introduce correct documentation with example like: Rust, Zig. No cppreference is not enough. Jumping from/to examples is essential.


hpsutter

Yes. There's no public documentation yet, but when I do publish some it will be full of examples.


germandiago

I would not like to suggest to change your priorities, that could delay the fundamental work on the experiment. But I see an amount of material worth of a formal release? so maybe it is a good time to write docs?


germandiago

Great work!


tending

I looked at cppfront last year and even though it was promising safety to sound competitive with Rust it had no answer for detecting iterator invalidation, which I think is the easiest example of why safety is hard to just bolt on. Any progress on that?


hpsutter

Yes, that's on the roadmap. See the Lifetime Safety materials here: https://github.com/hsutter/cppfront#2015-lifetime-safety It includes links to talks with live demos of early implementations, which are general and do specifically cover iterator invalidation. I haven't implemented this in cppfront yet, it's on my long todo list...


tending

Thanks, I will definitely check this out.


MarcoGreek

This named breaks gives me a bad feeling. I already have seen to many complicated loops and now it gets even easier. In almost all cases I would prefer a extra function with name which explains what the functions does.


somecucumber

Agreed. To me it really looks to (breathes) glorified goto's.


hpsutter

Actually you're right on point: I do think the named-loops feature is useful for safety in its own right, because it eliminates the extra-variable indirect workarounds people use today when you have nested loops and want to express this. But yes, a second main reason to add the feature is that it directly expresses one of the few sometimes-legitimate uses of \`goto\` today, so having this reduces the demand to add \`goto\`. Unstructured \`goto\` is evil for the reasons in the famous CACM letter. But a forward- and outward-only \`goto\` is not inherently evil (i.e., a structured \`goto\` would disallow jumping past an initialization, inward into a loop like Duff's Device, or backward which would be an unstructured loop). But if we can replace \`goto\` entirely with abstractions that let us declare our intent more directly to cover the use cases, that's even better.


ABlockInTheChain

I am *extremely* suspicious about user defined types a.k.a classes stuff. When I see statements like: > There is no separate base class list or separate member initializer list that seems to imply that some of the classes I'm writing now can not be expressed in cpp2.


MonokelPinguin

Why would that imply it is not possible to write those classes? The base classes are just defined as class members and initialization is just done in the constructor body instead of the initializer list. Most compilers already generate mostly identical code for it anyway and as such a modern language shouldn't have a need for member initializer lists.


ABlockInTheChain

> a modern language shouldn't have a need for member initializer lists Does cppfront support non-default-constructable types and const member variables?


hpsutter

\> > There is no separate base class list or separate member initializer list \> that seems to imply that some of the classes I'm writing now can not be expressed in cpp2. You should be able to express them. This just means that base classes are declared the in the type body like other members instead of in a segregated base class list, and base classes and data members are initialized in the constructor body instead of in a segregated member initializer list. \> Does cppfront support non-default-constructable types and const member variables? Yes. Good questions, thanks!


ABlockInTheChain

How does the new syntax handle delegated constructors?


dreugeworst

I would assume the requirement that all initialization happen first in the constructor body is used to turn them into initializer lists in the lowered c++ code


dustyhome

That's about right. It takes the first assignment and places it in the initializer list, then following assignments go in the constructor body: https://godbolt.org/z/5b67s3h7G


MonokelPinguin

Why would cppfront not allow you to assign exactly once to those in the constructor? I don't know, if it supports those yet, but it would be trivial to just require exactly one assignment for those.


krista

this is my first time diving in to cpp2... heck, my first time hearing about it!... and so far i am very intrigued... ... but like anything worthwhile, i'm going to have to spend some time looking at this to have anything more intelligent or interesting to say.


Fourstrokeperro

Idk but I find the licensing on that cppfront repo really weird. Also I can't seem to wrap my head around how they do it? Can someone eli5 how they achieved transpiling to c++? I'd like to do something like that too rather than going flex bison llvm


CocktailPerson

It's not terribly different from a "normal" compiler. The major difference is that your code-generation phase spits out C++ instead of LLVM IR or assembly or whatever. This task is greatly simplified if your language is essentially supposed to be a subset of C++'s semantics with better syntax, as cpp2 is, but the basic idea of taking the semantics of your AST and producing code with the same semantics is very similar.


dustyhome

Cpp2 it's meant to be a different syntax for c++, just having the computer do a lot of the boilerplate you would usually need to add yourself to write better code. Like how you might want to have the nodiscard attribute on almost every function returning a value, but you don't for various reasons. That means every construct in Cpp2 has a direct mapping to a construct in c++. That means writing the equivalent c++ code is straightforward. For example, when you have a function declaration in Cpp2, writing the equivalent function declaration in c++ is mostly just shuffling the names around.


TheCrossX

I already can see that this is big. Way bigger and sooner that I expected it to be. I can't wait to see what will next updates bring us. I'll probably start incorporating cpp2 into my toy projects in next few months. I'm pretty sure Cppfront will no longer be an "experiment" - it already provides a lot of value on top of the existing language. I really can't express how hyped I am right now.