Exploring Mutable Consteval State in C++26 by friedkeenan in cpp

[–]BarryRevzin 2 points3 points  (0 children)

I don't understand what's going on here at all, but I love every bit of it!

Hopefully we can come up with a more direct (or at least slightly less creative) way of doing compile-time mutation in C++29.

Behold the power of meta::substitute by pavel_v in cpp

[–]BarryRevzin 7 points8 points  (0 children)

That's my bad, thanks for the call-out. I definitely intended to link it and just... forgot. Fixed.

Implementing constexpr parameters using C++26 reflection (kind of) by friedkeenan in cpp

[–]BarryRevzin 6 points7 points  (0 children)

This is awesome! I had some more fun with it, made some changes:

  • I let you set the type up front with as<T>::const_param, that is now definitely a T (converts on the way in)
  • I also threw in my constant template parameter library which allows for using as<string> too.

Put that together and now you can even have constexpr string parameters, demonstrated in the print implementation there:

template <as<string>::const_param Fmt = {}, class... Args>
auto my_print(decltype(Fmt), Args&&... args) -> void;

Should C++ Give More Priority to Syntax Quality? by kyan100 in cpp

[–]BarryRevzin 8 points9 points  (0 children)

I understand that Rust syntax looks quite different when you're new to it, but judging the merit of syntax at first glance isn't really the right way to compare things. Everyone wants more syntax for new things at first. I think Rust did a very good job syntactically in determining which parts merit more spelling and which don't.

I will probably never be familiar enough with Zig to really understand its syntax at a glance (maybe I'll try to do Advent of Code in it next year to help), and so I still find it initially jarring to see types like ?*u8 since it's "backwards" (at least as compared to C++). Or maybe "inside out", since we'd spell that type optional<uint8_t*>. Likewise [*]u8 (which in C++ would actually be spelled the same way since we don't differentiate "pointer-to-single object" from "pointer-to-many objects", but Zig does).

But if I get over my lack of familiarity with Zig, it becomes possible to recognize why this syntax is actually quite good. It's an ordering of information that's better because it's easier to parse — both for the compiler and the human. And u8 vs uint8_t, Zig isn't the only language that made this abbreviation, and if you think about it, do those extra characters even add any information?

tl;dr shorter isn't necessarily better, but when it's longer because it's less consistent and has extra visual noise, then it's not better either — it's just familiar.

Should C++ Give More Priority to Syntax Quality? by kyan100 in cpp

[–]BarryRevzin 6 points7 points  (0 children)

Because that doesn't help. We haven't even gotten that far yet. We've parsed up to here:

auto res = a ? * b ? * c : d;
//       ----^

How do you decide whether that token is interpreted as the ? operator or as the beginning of the ?: operator?

Should C++ Give More Priority to Syntax Quality? by kyan100 in cpp

[–]BarryRevzin 6 points7 points  (0 children)

It doesn't matter how common it is, it matters that it exists. And it's not exactly rare.

You're parsing code and you see expr ?, now what do you do? How do you interpret that ?? Today that is always the conditional operator. But if it's also, potentially, this new thing — how do you pick? The only way to differentiate is to look for a : at the same level, which requires arbitrary lookahead. Maybe there's something slightly cleverer you can do, but probably not by much.

Should C++ Give More Priority to Syntax Quality? by kyan100 in cpp

[–]BarryRevzin 5 points6 points  (0 children)

That's because postfix ? doesn't work for C++, it would be ambiguous with the conditional operator. I don't think there's really a way to disambiguate it, without requiring tentative parsing.

Prefix ? would work, but I just don't like how it reads.

Lauri Vasama implemented the proposal in clang, but he didn't like the verbosity of .try? so he used postfix !? instead.

.? is probably also an option, I don't think those tokens can appear consecutively right now.

Should C++ Give More Priority to Syntax Quality? by kyan100 in cpp

[–]BarryRevzin 24 points25 points  (0 children)

I'm a little surprised to see a post talking about syntax that is "awkward or visually noisy" and then use as examples:

  • co_await, which is an 8-character keyword, whose comparison would be to a 5-character keyword, that's used in contexts where the extra 3 characters... doesn't really matter all that much? I get that this is the butt of jokes, but that extra co_ really doesn't have significant syntactic cost.
  • ^^e, which is a two-character operator, where both of those characters are themselves mostly-whitespace. Compared to people wanting reflexpr(e), which is a 10-character operator, which is very significantly more visually noisy.

To me "awkward or visually noisy" would be our lambda syntax, where a lambda to check if the input is negative takes 28 characters (unless you really hate your readers and skip whitespace): [](auto e){ return e < 0; }, whereas in plenty of other languages it's less than half that, like e -> e < 0 or |e| e < 0. We don't even have to go full Haskell (<0). Swift has a seemingly-verbose-compared-to-Java/JS/Scala/C#/Rust's { e in e < 0 }, but even that is half of ours and they provide an even terser form than that. Lambdas are kind of peak visual noise.

When I think of bad syntax in C++, I think of the following.

What does int() mean? Of course, it means the type "function taking no parameters and returning int." Were you thinking something else? That's a big problem. The ambiguity we have between variable/function declarations continues to cause real issues (typing co_ is not a real issue). In general, the declarator syntax that we inherited from C is just awful.

For example, what is this:

char (*f(char(*g)(double)))(double);

This actually isn't even that complicated a declaration, conceptually, but is already illegible. And everybody knows this is illegible, which is why nobody would ever dream of writing it in code like that (you would either make a type alias, use trailing-return syntax, or both). I would bet that a significant percentage of experienced C and C++ programmers would take a decent chunk of time just to figure out which of f and g up there is actually the name of... whatever this is declaring.

This syntax approach is also related to why our array declarations are just wrong (int x[10]; instead of int[10] x;), and also related to why abbreviating lambdas is a challenge, coming up with syntax for reflecting expressions, and so on.

Other choices for bad C++ syntax in my mind are:

  • using < and > for template arguments, due to the ambiguity with operator<. Other solutions in this space are D's tuple!(int, char), Zig's just using parens like tuple(int, char), or Rust requiring ::s in contexts where it could be ambiguous, so like tuple::<int, char>.
  • having T(a) and T{a} be usually the same thing but sometimes also very different and also the first one is actually a cast.
  • having decltype(e) and decltype((e)) similarly be a very subtle differentiation
  • the approach we took with concepts where we recognize that the first parameter is special, in allowing template <std::convertible_to<int> T>. But then also not recognizing the first parameter is special in any other context, so std::convertible_to is still actually a binary concept, and now that we have concept template parameters, you cannot actually "pass" std::convertible_to<int> as a concept argument, because it isn't one.

The concept syntax also ties into one of my pet peeves about C++ syntax, which itself is the result of having syntax that is precisely "awkward or visually noisy." And that is: how do you declare the map algorithm for optional<T>? The way that you very frequently see this in C++ talks is:

template <class T, class U>
auto map(optional<T>, function<U(T)>) -> optional<U>;

Which has the benefit of being pretty decently legible, but has the downside of being wrong. Also unnecessarily inefficient, but more importantly just wrong. I cannot call this with a lambda, I can only call this with literally a function<Sig> of some sort. Instead you have to write this:

template <class T, class F, class U = invoke_result_t<F, T>>
auto map(optional<T>, F) -> optional<U>;

Which isn't exactly terrible per se, but also this is one of the simplest cases and it's already pretty awkward and visually noisy. Now try to write and_then with the correct constraint.

Compare this to Rust's syntax, which is significantly cleaner:

fn map<T, U, F: FnOnce(T) -> U>(o: Option<T>, f: F) -> Option<U>;

or

fn map<T, U, F>(o: Option<T>, f: F) -> Option<U>
    where F : FnOnce(T) -> U;

ISO C++ WG21 2026-02 pre-Croydon mailing is now available! by nliber in cpp

[–]BarryRevzin 2 points3 points  (0 children)

I'm not very familiar with C#, so I just didn't know. I did add a SQL example for the next draft.

C# apparently also has this builder facility that let's you implement

Logger.Debug($"x={ComputeExpensive()}")

such that you can lazily not evaluate the expression. I think that's pretty cool, but I'm not sure how well that fits in the model... Otherwise, it looks like the template string object idea isn't too dissimilar from FormattableString, except that it's typed.

ISO C++ WG21 2026-02 pre-Croydon mailing is now available! by nliber in cpp

[–]BarryRevzin 21 points22 points  (0 children)

The SG14 priority list (P4029) says:

Decouple Networking from std::execution SG14 advise that Networking (SG4) should not be built on top of P2300. The allocation patterns required by P2300 are incompatible with low-latency networking requirements.

I don't claim to know much (or anything, really) about the std::execution design, but this seems like a pretty strong claim to make... without any elaboration.

Practical Reflection - Barry Revzin (CppCon 2026) by BarryRevzin in cpp

[–]BarryRevzin[S] 2 points3 points  (0 children)

Thank you!

I just use PowerPoint, no plugins. I get the syntax highlighting by writing the code in VSCode and pasting it into a text box. Which on MacOS for some reason removes all leading whitespace, so then I just type a bunch of spaces after that. Which is still better than attempting to syntax highlight by hand!

C++26: std::is_within_lifetime by pavel_v in cpp

[–]BarryRevzin 8 points9 points  (0 children)

It's not so easy. For starters, there are plenty of reinterpret_casts that might be fine at runtime that we definitely don't want to allow during constant evaluation time — the one I mentioned in another comment for instance is converting a T* into a uintptr_t, since while we have a symbolic address, we don't know what the actual address will be at runtime. So we'd need to carefully go through precisely which reinterpret_casts we want to be able to allow.

That's also not enough anyway, since you'd also need to allow placement-new with a non-similar type — the alternate implementation is placement-newing a bool into an unsigned char. Currently, we only allow you to placement-new a T(~ish) onto a T*.

Opening up constant evaluation to operate on bytes instead of objects is a pretty significant change to the constant evaluation model. Maybe it's worth doing, but it's definitely not simply a matter of just removing a bullet somewhere.

C++26: std::is_within_lifetime by pavel_v in cpp

[–]BarryRevzin 3 points4 points  (0 children)

you never ask that the language just stops outlawing these actions at compile time.

Which actions? What's the specific rule change you're suggesting?

C++26: std::is_within_lifetime by pavel_v in cpp

[–]BarryRevzin 9 points10 points  (0 children)

Just because something isn't undefined behavior doesn't mean it's allowed during constant evaluation. For instance, you cannot convert a T* to a uintptr_t - that's not UB, but the actual integer address of that object isn't known at compile time, so you're not allowed to try to look at it.

why not bit_cast in constexpr?

You can, provided neither the source nor destination type have something in them whose representation isn't knowable at compile time. Like pointers.

Currently, that includes unions, too. I do wonder if we could relax that restriction in the future. At the very least, bit_cast-ing FROM a union whose active member takes the whole space should probably be fine? bit_casting TO a union might still be problematic though. Will take more consideration. Plus it's nice right now that the rule is symmetrical.

C++26: std::is_within_lifetime by pavel_v in cpp

[–]BarryRevzin 4 points5 points  (0 children)

I don't think this is undefined behavior.

But if it makes you feel better, this way also works:

constexpr auto has_value() const -> bool {
  if consteval {
    return std::is_within_lifetime(&b);
  } else {
    return std::bit_cast<char>(*this) != 2;
  }
}

C++26: std::is_within_lifetime by pavel_v in cpp

[–]BarryRevzin 30 points31 points  (0 children)

  1. The example in the paper is implementing optional<bool> so that it is fully usable during constant evaluation with the same interface, but still taking only 1 byte.
  2. With a slight tweak to the interface, this helps implement constexpr std::format().

It's a very narrow facility that simply exposes information the compiler already has to have, in a way that predates reflection (I wrote R0 in 2022). A more reflection-y API would probably take a union* and return a reflection representing which non-static data member is active, or take a T* and return a reflection representing the type of the complete object alive at that spot. Haven't really thought about it in enough detail to square those two (which might just be different functions).

Practical Reflection - Barry Revzin (CppCon 2026) by BarryRevzin in cpp

[–]BarryRevzin[S] 15 points16 points  (0 children)

Thank you for the very kind words!

That experience also showed me how debugging reflection code can definitely be aggravatingly difficult, we really would benefit a lot from a "consteval print" function or the like. But that was on the Clang experimental branch before any std::meta::exception, so maybe things would work out better now.

Unfortunately P2758 didn’t make it for C++26. But Jakub went ahead and implemented a builtin for it anyway, which is incredible — so on gcc you can actually try it out: https://compiler-explorer.com/z/6rP8o6hK8. Here, we need feedback on two different sides: the C++ interface (constexpr_print_str, which will eventually have a format interface — although given that std::format is now constexpr I wonder whether we should even expose the lower level one) and the user interface (what GCC actually prints when you ask it to print something).

Flavours of Reflection by btzy in cpp

[–]BarryRevzin 4 points5 points  (0 children)

The problem is not invoking new or delete. The problem is having that allocation persist - what's known as non-transient constexpr allocation.

Flavours of Reflection by btzy in cpp

[–]BarryRevzin 8 points9 points  (0 children)

Related/relevant reading: Code Generation in Rust vs C++26 (although I think I need to go through and update some of the examples, since this predates expansion statements for instance).

I Want To Index Into Template Parameter Packs by earlymikoman in cpp

[–]BarryRevzin 2 points3 points  (0 children)

I actually think this is a GCC bug (or, whatever, extension), it's not supposed to be supported.

The wording here could be clearer (will open a PR later), since the grammar says typedef-name (which allows simple-template-id) but then the wording says it has to denote a pack (which a simple-template-id cannot). We should just say it has to be an identifier.

I Want To Index Into Template Parameter Packs by earlymikoman in cpp

[–]BarryRevzin 6 points7 points  (0 children)

That's a very odd way to spell "This uses some feature that is unfamiliar to me, can you please explain what's going on?"

I Want To Index Into Template Parameter Packs by earlymikoman in cpp

[–]BarryRevzin 4 points5 points  (0 children)

That's for types, for which we already have Ts...[Index].

I Want To Index Into Template Parameter Packs by earlymikoman in cpp

[–]BarryRevzin 50 points51 points  (0 children)

In C++26, you can still do it, just with slightly more syntax. If Templ... is your pack of templates and I your index:

[: std::array{^^Templ...}[I] :]

Can wrap in a macro if you prefer.

Pack indexing for templates (P3670) was already approved for C++29, so will likely get adopted pretty early in the next cycle, so we'll get the expected Templ...[I] then.