F-strings for C++26 proposal [pdf]
34 comments
·February 2, 2025edflsafoiewq
HeliumHydride
Yes. The basic idea is that there's a specifier that allows a formatted string to transparently decay into an ordinary string (à la array-to-pointer decay) so that "auto" doesn't produce dangling references, and so that chains of more than one implicit conversion can take place.
mkoubaa
Tangent: this sort of thing can be implemented without any change to libc++ (the runtime). Updates to compiler versions are sometimes postponed by users with big codebases that treat a libc++ change as something major.
Why don't we see gcc or clang or msvc back porting stuff like this to an older version with a sort of future tag. It's normal to see __future__ in the python ecosystem, for instance.
tart-lemonade
If a codebase is fragile enough that libc++ changes have to be assumed breaking until proven otherwise, why take the risk? Presumably the application already has a "standard" way of formatting strings. If it ain't broke yada yada
mkoubaa
It's not about assumed breaking, it's that when you upgrade libc++ you can become incompatible at runtime with your distro or any other number of libraries outside your control in ways that are difficult to detect
puffybuf
I'm pretty sure boost::format can do this, though not inline in the string. Do we really need more complexity in cpp? isn't it complex enough?
mkoubaa
This is the sort of change that adds complexity to the language but reduces complexity in the code written in the language. We take those
hackyhacky
> This is the sort of change that adds complexity to the language but reduces complexity in the code written in the language. We take those
An admirable statement of policy, but I'm not sure it's possible. Adding complexity to the language means there are more gotchas and edge-cases that a programmer must consider, even if they don't use the feature in question.
puffybuf
how would this work with internationalized strings? especially if you have to change the order of things? You'd still need a string version with object ordering I would think
edflsafoiewq
f-strings are not an internationalization library.
mkoubaa
I'm skeptical that people would want to do this in a single expression.
HeliumHydride
C++ also has std::format, which was introduced in C++20. This is just sugar on top of it, except it also returns a container type so that printing functions can have overloads that format into a file or stream directly from an f-string, instead of going through the overhead of a temporary string.
richrichardsson
So it's less complex bringing in a 3rd party library and having to pass arguments?
fmt library can also do something similar, but still requires the complexity of adding the library and passing arguments.
nikhilsimha
just skimmed the proposal, dont see how inline rendered f-strings are more complicated than the alternative.
ggm
I'm going to make an asinine prediction. We will be exploring F-strings in future languages in 100 years time, encountering the same problems and questions.
I still use printf semantics in Python3 despite trying to get with the program for symbolic string/template logic. I don't need to be told it's better, I need some Philip-K-Dick level brain re-wiring not to reach for
"%d things I hate about f-strings\n" % (int(many()))
modes of thinking.amluto
This links to a “decays_to” proposal:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p33...
And observes that this additional feature is needed to avoid dangling references. And, as a long time C++ programmer, this illustrates one of the things I dislike most about C++. In most languages, if you make a little mistake involving mixing up something that references something else with something that contains a copy, you end up with potential overhead or maybe accidental mutation. In Rust, you get a compiler error. In C++, you get use-after-free, and the code often even seems to work!
So now we expect people to type:
auto s = f"{foo}";
And those people expect s to act like a string. But the designers (reasonably!) do not want f to unconditionally produce an actual std::string for efficiency reasons, so there’s a proposal to allow f to produce a reference-like type (that’s a class value, not actually a reference), but for s to actually be std::string.But, of course, more advanced users might know what they’re doing and want to bypass this hack, so:
explicit auto s = f"{foo}";
Does what they programmer actually typed: s captures foo by reference.What could possibly go wrong?
(Rust IMO gets this exactly right: shared xor mutable means plus disallowing code that would be undefined behavior means that the cases like this where the code might do the wrong thing don’t compile. Critically, none of this actually strictly requires Rust’s approach to memory management, although a GC’d version might end up with (deterministic) runtime errors instead unless some extra work is done to have stronger static checking. And I think other languages should learn from this.)
edflsafoiewq
IOW I believe it's the same thing as Rust's format_args! macro, but trying to get away without needing a separate format! macro by using implicit conversions.
tialaramex
std::format_args! gets you a Arguments<'a> which we'll note means it has an associated lifetime.
Today-I-learned, Arguments<'a> has a single useful function, which appeared before I learned Rust but only very recently became usable in compile time constants, as_str() -> Option<&'static str>
format_args!("Boo!").as_str() is Some("Boo!")
If you format a literal, this always works, if you format some non-literal the compiler might realise the answer is a compile time fixed string anyway and give you that string, but it might not even if you think it should and no promises are given.
thaumasiotes
> But, of course, more advanced users might know what they’re doing and want to bypass this hack, so:
explicit auto s = f"{foo}";
> Does what they programmer actually typed, so s captures foo by reference.Wouldn't this problem be best solved by... not declaring s to have a guess-what-I-mean type? If you want to be explicit about the type of s, why not just say what that type is? Wouldn't that be even more explicit than "explicit auto"?
amluto
A general issue with C++ (and many statically typed languages with generic) is hilariously long type names that may even be implementation details. Using auto can be a huge time saver and even necessary for some generic code. And people get in the habit of using it.
dgfitz
Somehow I manage to get by just fine with c++11. I have refactored more than a few codebases that use 17 or greater.
Strangely, the codebase became more maintainable afterwards.
ryandrake
Came here to post the same thing. C++11 was a major and practical step up from previous versions. I haven't seen anything in future standards that looked like it a tool I'd use day-to-day building actual production software. Much of the subsequent versions added things probably interesting to compiler and language academics. "Default constructible and assignable stateless lambdas?" Really?
vitus
Off the top of my head, C++17 brought slicker notation for nested namespaces, digit separators for numeric literals (so you can more easily read 1'000'000'000), improvements in type deduction for pairs / tuples (so std::make_pair / make_tuple are basically unnecessary now), guarantees in the standard for copy elision / return value optimization in specific circumstances,. Oh, and structured bindings (so you can now write `for (const auto& [key, value] : map) { ... }`).
edit: I guess digit separators came in C++14, I'm always a little fuzzy there since at work, we jumped straight from 11 -> 17.
C++20 brought a feature that C had decades prior: designated initializers, except it's in a slightly crappier form. Also, spaceship operator (three-way comparison).
Looking at cppreference, it looks like C++17 also brought if constexpr, and standardized a bunch of nonstandard compiler extensions like [[fallthrough]]. C++20 continued standardizing more of those extensions, and also brought concepts / constraints, which are a lot easier to use than template metaprogramming.
You're at least somewhat right though -- none of these are paradigm shifts as C++11 was compared to C++03 (especially with the notion of ownership, especially in the context of std::unique_ptr and std::move).
edflsafoiewq
17 is worth it for std::filesystem alone. It also has optional and variant.
unit149
[dead]
dataflow
constexpr in 11 vs 14 was night and day difference.
FpUser
To me moving from C++11 to 17 and then 20 was just a matter of convenience. When digging on how to do this and that I've found few things that just saved my time here and there. Also couple of valuable libs I wanted to use required newer C++ versions.
andyg_blog
I agree that we should have safe-by-default "decay" behavior to a plain ol std::string, but I'm also picking up that many aren't certain it's a useful syntactic sugar in top of the fmt lib? Many other languages have this same syntax and it quickly becomes your go-to way to concatenate variables into a string. Even if it didn't handle utf-8 out of the box, so what? The amount of utility is still worth it.
neonsunset
Reinventing C#’s FormattableString and interpolated string handlers :)
mixmastamyk
So, the f-string in Python is "spelled" that way because another leading character was the only ASCII syntax left for such a thing. It's odd that PRQL and now potentially C++ might copy it. In the PRQL case it was a new thing so they could have chosen anything, double quotes (like shell interpolation) or even backticks, that seem to make more sense.
Also the f- prefix was supposed to be short for format and pronounced that way. But "eff" caught on and now devs the world over are calling them "eff strings" ... funny. :-D
efitz
When I saw the title I thought “F-strings” might be some novel variant of P—strings. I was disappointed that this is just about formatting. I really would prefer safer string handling in modern C/++
So the f-string literal produces a basic_formatted_string, which is basically a reified argument list for std::format, instead of a basic_string. This allows eg. println to be overloaded to operate on basic_formatted_string without allocating an intermediate string
In exchange we have the following problems There are two other proposals to fix these problems.