Skip to content(if available)orjump to list(if available)

C++26: more constexpr in the standard library

WalterBright

D does compile time function evaluation every time there's a ConstExpression in the grammar. It did this back in 2007. It required no changes to the grammar or syntax, it just enhanced constant folding to be able to do function calls.

I don't understand why it is so complex in C++.

phire

We aren't talking about just compile time function evaluation of ConstExpressions. C++ had that all the way back in C++11, and many compilers were doing CTFE long before that as an optimisation (but as a programmer you couldn't really control when that would happen)

Compilers are always allowed to CTFE than what the C++ standard specifies, many do. The standard is just moving the lower bar for what all compilers must now evaluate at compile time, and what c++ programmer may expect from all compilers.

Since C++11, they have been massively extending the subset of the language that can be evaluated at compile time. C++14 allowed compiletime evaluation of loops and conditionals, along with local variables. C++20 allowed CTFE functions to do memory allocation (as long as it was also freed) along with calling object constructors/deconstructors.

The main new thing in C++26 is the ability to do CTFE placement new, which is something I remember missing the last time I tried to do fancy C++ stuff. Along with marking large chunks of the standard library as constexpr.

JonChesterfield

It has been a very exciting fifteen years. As a minor counter argument, other languages will let you run any code at compile time. Because it's code. And it can run whenever you want. C++ has that too I suppose, you just have to weave some of your control flow through cmake.

WalterBright

Function calls, loops, conditionals, local variables, memory allocation (via new), constructors/destructors, etc., were all there in D2007.

jcelerier

A big discussion item in constexpr in C++ was evaluation of floating-point at compile-time. Because depending on your current running CPU flags, you can easily end up in a case where a function do not give the same result whether at run-time vs at compile-time, confusing users. How does that work in D (and for that matters, any other language with compile-time evaluation) ?

flohofwoe

Tbf, C and C++ did constant folding for function calls since forever too (as well as pretty much any compiled language AFAIK), just not as a language feature, but as an optimizer feature:

https://www.godbolt.org/z/Kja5jrWo5

>I don't understand why it is so complex in C++

Because C++ is a completely overengineered boondoggle of a language where every little change destabilizes the Jenga tower that C++ had built on top of C even more ;)

WalterBright

I'm not seeing it:

    int sum(int a, int b) { return a + b; }
    _Static_assert(sum(1,2) == 3, "message");

    gcc -c -O x.c

    x.c:2:17: error: expression in static assertion is not constant
  _Static_assert(sum(1,2) == 3, "message");

mlvljr

[dead]

JonChesterfield

Constexpr as a keyword which slowly applies in more places over decades is technically indefensible. It has generated an astonishing quantity of work for the committee over the years though which in some sense is a win.

I think there's something in the psychology of developers that appreciates the byzantine web of stuff which looks like it will work but doesn't.

zombot

How is it technically indefensible?

JonChesterfield

Instead of annotating functions with "maybe this will be evaluated by the compiler", you can not do that, and the compiler will still evaluate them as a QoI thing. The usual justification is that it's a helpful comment for developers. Consteval being much the same idea. And constinit also the same idea.

An alternative design would be "global initialisations are evaluated at compile time where possible, like everything else, and you don't need to annotate anything to get that". Which would require exactly the same compiler work as constexpr but wouldn't leave that word scattered around codebases in similar fashion to register.

mcdeltat

I always wondered why constexpr needs to be an explicit marker. We could define a set of core constexpr things (actually this already exists in the Standard) and then automatically make something constexpr if it only contains constexpr things. I don't want to have to write constexpr on every function to semantically "allow" it to be constexpr even though functionally it already could be done at compile time... Same story with noexcept too.

vinkelhake

One reason which matters with libraries is that slapping constexpr on a function is, in a way, a promise by the author that the function is indeed meant to be constexpr.

If the author then changes the internals of the function so that it can no longer be constexpr, then they've broken that promise. If constexpr was implicit, then a client could come to depend on it being constexpr and then a change to the internals could break the client code.

Disclaimer: this is not a normative statement.

WalterBright

Adding unittests solves that issue nicely, along with many other issues with a library function diverging from its documented purpose.

kccqzy

I think being explicit is a good thing in C++. Suppose there is not constexpr in C++ and the following works:

    inline int foo(int x) { return x + 42; }
    int arr[foo(1)];
I think it would qualify as spooky action-at-a-distance if modifying foo causes arr to be malformed. And if they are in different libraries it restricts the ways the original function can be changed, making backwards compatibility slightly harder.

tialaramex

This would make more sense if constexpr was actually constant like say, Rust's const.

The Rust const fn foo which gives back x + 42 for any x, is genuinely assured to be executed at compile time when given a constant parameter. If we modify the definition of foo so that it's not constant the compiler rejects our code.

But C++ constexpr just says "Oh, this might be constant, or it might not, and, if it isn't don't worry about that, any constant uses will now magically fail to compile", exactly the spooky action at a distance you didn't want.

When originally conceived it served more or less the purpose you imagine, but of course people wanted to "generalize" it to cover cases which actually aren't constant and so we got to where we are today.

Calavar

Slapping the constexpr keyword on a function is useless by itself, but it becomes useful when you combine it with a constexpr or constinit variable. Which is not all that different from Rust:

    // C++
    constexpr Foo bar() { /* ... */ }
    constexpr Foo CONSTANT = bar(); // Guaranteed to be evaluated at compile time
    constinit Foo VARIABLE = bar(); // Guaranteed to be evaluated at compile time

    // Rust
    const fn bar() -> Foo { /* ... */ }
    const CONSTANT: Foo = bar(); // Guaranteed to be evaluated at compile time
    static VARIABLE: Foo = bar(); // May or may not be evaluated at compile time
So Rust is actually less powerful than C++ when it comes to non-constant globals because AFAIK it doesn't have any equivalent to constinit.

pjmlp

True, but C++ being as it is, we now also have consteval and constinit.

WalterBright

That does work in D, has for 18 years now, and is one of D's best loved features. Nobody has asked for the constexpr keyword.

112233

Why? How is changing constness of foo different from changing return type of foo, or changing set of thrown exceptions, or argument set?

Especially now that it is a good practice to define c++ functions like

    auto f(auto &&x) -> decltype(1+x)

?

WalterGR

username923409

That's a different article

zombot

Exactly, one for the core lang, one for the stdlib. But the same overall topic.

WalterGR

You’re right. My mistake.

nokeya

And in the wild (supported by all major compilers) we will see this somewhere around 2040…

mcdeltat

And bug free, 2060.

(Back in C++20 days I had a terrible habit of finding bugs in MSVC's implementations of metaprogramming features, after they claimed to be feature complete on C++20. Probably because people use these features less. Even now I occasionally receive emails about those bugs they've finally fixed.)

nly

Not if you run RHEL:

dnf install gcc-toolset-X

pjmlp

GCC only started supporting C++20 modules in a usable form, last month, and there are still parts missing from C++20.

So expect at very least 2026 + 5 => 2031 for that command to provide a complete C++26 development experience.

ender341341

The compiler writers have had a ton of issues implementing modules and aren't particularly excited for them (the committee seems to have forgot the lessens learned from c++98 and not having full implementations for crazy hard things)

on the other hand constexpr changes tend to be picked up pretty quickly, and a lot of them tend to be things that the compiler/stl authors themselves are asking for.

WalterBright

C++ should have just copied D modules (and the modules that ImportC supports).

gpderetta

Sure but this is about constexpr, not modules.

null

[deleted]

gitroom

love seeing all the constant updates but sometimes i feel like the biggest pain points just stick around way too long - you ever feel like these standards chase the wrong problems sometimes?

kreetx

What are the right problems?

Davidbrcz

Sanity, language complexity,

Night_Thastus

"complex" isn't going anywhere. Complexity in languages only rises with age, and C++ has it especially because it's meant to be very general-purpose rather than specialized. Everyone uses C++ in different ways for different purposes, leading to additional complexity to keep everyone happy.

"sanity" might refer to default behaviors. If it does, there's not a lot you can do there either. You can't go changing fundamentals of how the language works, because backwards compatibility is paramount. There would be riots if a new standard broke significant chunks of legacy code - even for the better.

kubav027

One of my first tasks in my carrier was implementing stable sort to incomplete stl implementation. In 3 years no one used it. This so niche it should not be part of C++ standard.

brooke2k

seeing the words "constexpr sorting" makes all the compile-time sirens go off in my head

mkoubaa

We demand an opt out!

dvratil

Amazing, now could I just get a way to do asynchronous network requests in two lines of code, like I have with other languages?

Honestly, it seems to me like the committee is constantly chasing the easy bits but is failing to address the bigger issues in the language and, more importantly, the standard library.

binary132

It’s very different to ask for that from a language like Go or Python vs a language like C++. C++ is for interacting with system APIs and resources, on ANY system. Standardizing networking would require that every targetable host must have a common interface. Merely bridging win32, macos, bsd, android, and Linux is hard enough, without regard to all the possible platforms a C++ user might be interested in targeting.

The more you add to the standard, the narrower the platform support gets. Should we also force C++ to only be able to target 64-bit hosts? How about requiring hardware vector support? See what I’m getting at?

If you just want to make an async nw call, there are lots of things you can do that in already. If you want to write an async nw driver or library, then maybe you should use C++.

lavalida

Boost.asio and Boost.cobalt both allow you to do this. You could even implement the coroutine traits yourself if you don't want to use these libraries.

int_19h

co_await etc is there, now it's a matter of libraries picking that up.

On Windows, you can do this today:

  auto response {co_await httpClient.GetStringAsync(uri)};

EliRivers

Sounds like you should use those other languages. Right tool for the right job.