What's new

Welcome to roeaw | Welcome My Forum

Join us now to get access to all our features. Once registered and logged in, you will be able to create topics, post replies to existing threads, give reputation to your fellow members, get your own private messenger, and so, so much more. It's also quick and totally free, so what are you waiting for?

Autumn replace – Sutter’s Mill

Hoca

Administrator
Staff member
Joined
Mar 22, 2024
Messages
190
Reaction score
0
Points
6
For the reason that 2022-12-31 year-end mini-update and the 2023-04-30 spring replace, progress has continued on cppfront. (In the event you don’t know what this private venture is, please see the CppCon 2022 talk on YouTube for an outline, and the CppNow 2023 talk on YouTube for an interim replace.)

I’ll be giving a significant replace next week at CppCon. I hope to see lots of you there! Within the meantime, listed here are some notes about what’s been occurring because the spring replace publish, together with:

  • Acknowledgments and thanks
  • Began self-hosting
  • No knowledge left behind: Obligatory specific discard
  • requires clauses
  • Generalized aliases+constexpr with ==
  • Protected enum and flag_enum metafunctions
  • Protected union metafunction
  • What’s subsequent

Acknowledgments: Thanks!​



Thanks to all these people who’ve participated within the cppfront repo by opening points and PRs, and to many extra who participated on PR opinions and remark threads! These contributors symbolize folks from highschool and undergrad college students to full professors, from business builders to convention audio system, and from each continent besides Antarctica.

Began self-hosting​


I haven’t spent numerous time but changing cppfront’s personal code from at present’s syntax 1 to my alternate syntax 2 (which I’m calling “Cpp1” and “Cpp2” for brief), however I began with all of cppfront’s reflection API and metafunctions which at the moment are largely written in Cpp2. Right here’s what that mirror.h2 file compilation appears like when compiled on the command line on my laptop computer:



However notice you may nonetheless construct cppfront as all-today’s-C++ utilizing any pretty current C++20 compiler as a result of I distribute the sources additionally as C++ (simply as Bjarne distributed the cfront sources additionally as C).


No knowledge left behind: Obligatory specific discard​


Initialization and knowledge circulate are elementary to secure code, so from the outset I ensured that syntax 2 assured initialization-before use, I made all changing constructors specific by default… and I made [[nodiscard]] the default for return values (1-min talk clip).

The extra I thought of [[nodiscard]], the extra decided I used to be that knowledge must not ever be silently misplaced, and data-lossy operations must be specific. So I’ve determined to strive an aggressive experiment:

  • make “nodiscard” the regulation of the land, implicitly required on a regular basis, with no opt-out…
  • together with when calling present C++ libraries (together with std::) that have been by no means designed for his or her return values to be handled as [[nodiscard]]!

Now, I wasn’t completely loopy: See the Design note: Explicit discard for particulars on how I first surveyed different languages’ designers about expertise of their languages — notably C#, F#, and Python. Particularly, F# does the identical factor with .NET APIs — F# requires specific |> ignore to discard unused return values, together with for .NET APIs that have been by no means designed for that and have been largely written in different languages. Don Syme instructed me it has not been a major ache level, and that was encouraging, so I’m following go well with.

My expertise to date is that it’s fairly painless, and I write about one specific discard for each 200 strains of code, even when utilizing the C++ commonplace library (which cppfront does pervasively, as a result of the C++ commonplace library is the solely library cppfront makes use of). And, to date, each time cppfront instructed me I needed to write an specific discard, I discovered one thing helpful (e.g., earlier than this I by no means realized that emplace_back started to return something since C++17! push_back still doesn’t) and I discovered I appreciated that my code explicitly self-documented it was not taking a look at output values… my code seemed higher.

The best way to do an specific discard is to assign the end result to the “don’t care” wildcard. It’s unobtrusive, however specific and clear:



_ = vec.emplace_back(1,2,3);

Now all Cpp2-authored C++ features are emitted as [[nodiscard]], besides just for project and streaming operators as a result of these are designed for chaining and each chain at all times ends with a discarded return.

And the entire language hangs collectively properly: Express discard works very naturally with inout and out parameters too, not simply return values. In case you have a neighborhood variable x and move it to an inout parameter, what if that’s the final use of the variable?



{
x := my_vector.start();
std::advance(x, 2);
// ERROR, if param is Cpp2 'inout' or Cpp1 non-const '&'
}

On this instance, that decision to std::advance(x, 2); is a particular final use of x, and so Cpp2 will routinely move x as an rvalue and make it a transfer candidate… and presto! the decision received’t compile as a result of you may’t move an rvalue to a Cpp2 inout parameter (the identical as a Cpp1 non-const-& parameter, so this accurately detects the output unwanted effects additionally when calling present C++ features that take references to non-const). That’s a function, not a bug, as a result of if that’s the final use of x which means the perform is just not taking a look at x once more, so it’s ignoring the “out” worth of the std::advance(x, 2) perform name, which is strictly like ignoring a return worth. And the steering is identical: In the event you actually meant to do this, simply explicitly discard x‘s last worth:



{
x := my_vector.start();
std::advance(x, 2);
_ = x; // all proper, you stated you meant it, keep it up then...
}

Including _ = x; afterward naturally makes that the final use of x as a substitute. Drawback solved, and it self-documents that the code actually meant to disregard a perform’s output worth.

I actually, actually like how my C++ code’s knowledge circulate is specific, and totally protected and secure, in syntax 2. And I’m more than happy to see the way it simply works naturally all through the language — from common assured initialization, to specific constructors by default, to banning implicitly discarding any values, to uniform therapy of returned values whether or not returned by return worth or the “out” a part of inout and out parameters, and all of it working additionally with present C++ libraries in order that they’re safer and nicer to make use of from syntax 2. Information is at all times initialized, knowledge isn’t silently misplaced, knowledge circulate is at all times seen. Information is treasured, and it’s at all times secure. This feels proper and correct to me.

requires clauses​


I additionally added assist for requires clauses, so now you may write these on all templates. The cppfront implementation was already producing some requires clauses already (see this 1-min video clip). Now programmers can write their very own too.

This required a little bit of combating with a GCC 10 bug about requires-clauses on declarations, that was fastened in GCC 11+ however was by no means backported. However as a result of this was the one drawback I’ve encountered with GCC 10 that I couldn’t paper over, and since I may give a transparent diagnostic that just a few options in Cpp2 that depend on requires clauses aren’t supported on GCC 10, to date I’ve been in a position to retain GCC 10 as a supported compiler and emit diagnostics in the event you attempt to use these few options it doesn’t assist. GCC 11 and better are all wonderful and assist all Cpp2 semantics.

Generalized aliases+constexpr with ==


Within the April weblog publish, I discussed I wanted a solution to write sort and namespace aliases, and that as a result of all Cpp2 declarations are of the shape factor : sort = worth, I made a decision to strive utilizing the identical syntax however with == to indicate “at all times equal to.”



// namespace alias
lit: namespace == ::std::literals;

// sort alias
pmr_vec: <T> sort
== std::vector<T, std::pmr::polymorphic_allocator<T>>;

I believe this clearly denotes that lit is at all times the identical as ::std::literals, and pmr_vec<int> is at all times the identical as std::vector<int, std::pmr::polymorphic_allocator<int>>.

Since then, I’ve thought of how this must be finest prolonged to features and objects, and I spotted the necessities appear to overlap with one thing else I wanted to assist: constexpr features and objects. Which, in spite of everything, are features/objects that return/have “at all times the identical values” identified at compile time…



// perform with "at all times the identical worth" (constexpr perform)
increment: (worth: int) -> int == worth+1;
// Cpp2 permits you to omit { return } round 1-line our bodies

// object with "at all times the identical worth" (constexpr object)
forty_two: i64 == 42;

I notably wanted these with a purpose to write the enum metafunctions…

Protected enum and flag_enum metafunctions​


Within the spring replace weblog publish, I described the primary 10 working compile-time metafunctions I applied in cppfront, from the set of metafunctions I described in my ISO C++ paper P0707. Since then, I’ve additionally applied enum and union.

An important factor about metafunctions is that they’re compile-time library code that makes use of the reflection and code technology API, that lets the writer of an atypical C++ class sort simply choose right into a named set of defaults, necessities, and generated contents. This strategy is important to creating the language easier, as a result of it lets us keep away from hardwiring particular “further” sorts into the language and compiler.

In Cpp2, there’s no enum function hardwired into the language. As a substitute you write an atypical class sort and simply apply the enum metafunction:



// skat_game is declaratively a secure enumeration sort: it has
// default/copy/transfer building/project and <=> with
// std::strong_ordering, a minimal-size signed underlying sort
// by default if the person did not specify a kind, no implicit
// conversion to/from the underlying sort, actually no public
// building besides copy building in order that it might probably by no means
// have a price totally different from its listed enumerators, inline
// constexpr enumerators with values that routinely begin
// at 1 and increment by 1 if the person did not write their very own
// worth, and conveniences like to_string()... the phrase "enum"
// carries all that that means as a handy and readable
// opt-in, with out hardwiring "enum" specifically into the language
//
skat_game: @enum<i16> sort = {
diamonds := 9;
hearts; // 10
spades; // 11
golf equipment; // 12
grand := 20;
null := 23;
}

Contemplate hearts: It’s a member object declaration, but it surely doesn’t have a kind (or a default worth) which is often unlawful, but it surely’s okay as a result of the @enum<i16> metafunction fills them in: It iterates over all the information members and provides each the underlying sort (right here explicitly specified as i16, in any other case it could be computed because the smallest signed sort that’s large enough), and an initializer (by default one increased than the earlier enumerator).

Why have this metafunction on an atypical C++ class, when C++ already has each C’s enum and C++11’s enum class? As a result of:

  • it retains the language smaller and easier, as a result of it doesn’t hardwire special-purpose divergent splinter sorts into the language and compiler
    • (cue Beatles, and: “all you want is class (wa-wa, wa-wa-wa), all you want is class (wa-wa, wa-wa-wa)”);
  • it’s a greater enum than C enum, as a result of C enum is unscoped and never as strongly typed (it implicitly converts to the underlying sort); and
  • it’s a greater enum class than C++11 enum class, as a result of it’s extra versatile…

… take into account: As a result of an enumeration sort is now “only a sort,” it simply naturally may have member features and different issues that aren’t doable for Cpp1 enums and enum courses (see this StackOverflow question):



janus: @enum sort = {
previous;
future;

flip: (inout this) == {
if this == previous { this = future; }
else { this = previous; }
}
}

There’s additionally a flag_enum variation with power-of-two semantics and an unsigned underlying sort:



// file_attributes is declaratively a secure flag enum sort:
// identical as enum, however with a minimal-size unsigned underlying
// sort by default, and values that routinely begin at 1
// and rise by powers of two if the person did not write their
// personal worth, and bitwise operations plus .has(flags),
// .set(flags), and .clear(flags)... the phrase "flag_enum"
// carries all that that means as a handy and readable
// opt-in with out hardwiring "[Flags]" specifically into the
// language
//
file_attributes: @flag_enum<u8> sort = present;

Protected union metafunction​


And you may declaratively choose into writing a secure discriminated union/variant sort:



// name_or_number is declaratively a secure union/variant sort:
// it has a discriminant that enforces just one various
// could be energetic at a time, members at all times have a reputation, and
// every member has .is_member() and .member() accessors...
// the phrase "union" carries all that that means as a handy
// and readable opt-in with out hardwiring "union" specifically
// into the language
//
name_or_number: @union sort = {
identify: std::string;
num : i32;
}

Why have this metafunction on an atypical C++ class, when C++ already has each C’s union and C++11’s std::variant? As a result of:

  • it retains the language smaller and easier, as a result of it doesn’t hardwire special-purpose divergent splinter sorts into the language and compiler
    • (cue the Beatles earworm once more: “class is all you want, class is all you want…”);
  • it’s a greater union than C union, as a result of C union is unsafe; and
  • it’s a greater variant than C++11 std::variant, as a result of std::variant is tough to make use of as a result of its alternate options are nameless (as is the kind itself; there’s no solution to distinguish within the sort system between a variant<int,string> that shops both an worker id or worker identify, and a variant<int,string> that shops both a fortunate quantity or a pet unicorn’s dominant coloration).

Every @union sort has its personal type-safe identify, has clear and unambiguous named members, and safely encapsulates a discriminator to rule all of them. Positive, it makes use of unsafe casts within the implementation, however they’re totally encapsulated, the place they are often examined as soon as and be secure in all makes use of. That makes @union:

  • as simple to make use of as a C union,
  • as secure to make use of as a std::variant… and
  • as a bonus, as a result of it’s an atypical sort, it might probably naturally produce other issues regular sorts can have, comparable to template parameter lists and member features:


// a templated customized secure union
name_or_other: @union <T:sort> sort
= {
identify : std::string;
different : T;

// a customized member perform
to_string: (this) -> std::string = {
if is_name() { return identify(); }
else if is_other() { return different() as std::string; }
else { return "invalid worth"; }
}
}

primary: () = {
x: name_or_other<int> = ();
x.set_other(42);
std::cout << x.different() * 3.14 << "n";
std::cout << x.to_string(); // prints "42", however is authorized whichever various is energetic
}

What’s subsequent​


For the remainder of the 12 months, I plan to:

  • proceed self-hosting cppfront, i.e., migrate extra of cppfront’s personal code to be written in Cpp2 syntax, notably now that I’ve enum and union (cppfront makes use of enum class and std::variant pervasively);
  • proceed working my checklist of pending Cpp2 options and implementing them in cppfront; and
  • work with just a few personal alpha testers to begin writing a little bit of code in Cpp2, to alpha-test cppfront and in addition to alpha-test my (to date unpublished) draft documentation.

However first, one week from at present, I’ll be at CppCon to present a speak about this progress and why full-fidelity compatibility with ISO C++ is important (and what which means): “Cooperative C++ Evolution: Toward a TypeScript for C++.” I look ahead to seeing lots of you there!
 
Top Bottom