13 ms·
F-strings for C++26 proposal [pdf]
- puffybuf 2y agoI'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 2y agoThis is the sort of change that adds complexity to the language but reduces complexity in the code written in the language. We take those
- puffybuf 2y agohow 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
- mkoubaa 2y agoI'm skeptical that people would want to do this in a single expression.
- rerdavies 2y agoDo what? Allow translators to reorder the appearance of arguments in a translated format string? It's a completely routine (and completely necessary) feature when doing translations.
- edflsafoiewq 2y agof-strings are not an internationalization library.
- wakawaka28 2y agoThe question was, how would you use this if you have i18n requirements. Format strings are normally part of a translation. I think the bad answer is to embed the entire f-string for a translation as usual, except this can't work because C++ f-strings would need to be compiled. The better answer is, don't use f-strings for this because you don't want translators to monkey around with code and you don't want to compile 50 versions of your code.
- mkoubaa 2y agoC++ is such a narrow skillset that I'd rather not roll the dice on translators knowing what to do
- wakawaka28 2y agoEven if you told them, "just copy the names from the original string" it's still asking for trouble, and maybe even security holes if they don't follow instructions. But the biggest problem with the idea is surely that the strings need to be compiled.
- hackyhacky 2y ago> 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.
- verall 2y ago> 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. Since this is C++, this is not a problem we have to consider
- pjmlp 2y agoThis is a meme by now, yet it isn't as if Python 3.13 is a simple as Python 1.0, Java 23 versus Java 1.0, .NET 9 with C# 13 versus .NET 1.0 with C# 1.0 and a Framework reboot,....
- cylemons 2y agoC# has a lot of features but most of them feel like simple syntactic sugar that make the language a joy to use and they interact nicely together. C++ has lots of features that interact with each other in unexpected ways that could leak memory or access freed memory etc.
- pjmlp 2y agoC# has already enough material for pub Quiz, and no, not all of them are syntatic sugar, and require deep knowledge of the .NET runtime, and the way it interacts with the host platforms. I imagine you never went too deep into unsafe, cross language interop, lambda evolution since the delegate days, events infrastructure, pluggable GC, RCW/CCW, JIT monitoring, the new COM replacement, how the runtime and language features differ across .NET Framework, Core, .NET MicroFramework, UWP, AOT compilation, Mono, .NET standard versus Portable Class Libraries, CLS friendly libraries,... On top of that, all the standard frameworks that are part of a full .NET install on Visual Studio, expected that most C# developers know to at least have some passing knowledge on how to use them.
- HeliumHydride 2y agoC++ 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.
- rerdavies 2y agoI'm wonder what this mysterious application is that is doing heavy formatting of strings but can't afford the overhead of a temporary string, and therefore requires horrifying and inscrutable and dangerous language extensions.
- chlorion 2y agoBeing able to use string formatting without a heap is pretty cool. Rusts string formatting machinery does not require any heap allocations at all, you can for example impl fmt::Write for a struct that writes directly to a serial console byte-by-byte with no allocations, and then you have access to all of rusts string formatting features available to print over a serial console! I'm not sure about the horrifying and dangerous extensions part though, I'm not really a C++ expert so I don't know if there's a better way to do what they want to do.
- badmintonbaseba 2y agofreestanding maybe? Embedded apps often don't shy away from using something like printf, yet they don't like unnecessary allocations.
- gpderetta 2y agoFor example, an high performance logger can ship the tuple object to a background thread for actual formatting and I/O, after converting the capture to by-value. Formatting on the foreground thread would be a non-starter.
- nikhilsimha 2y agojust skimmed the proposal, dont see how inline rendered f-strings are more complicated than the alternative.
- richrichardsson 2y agoSo 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.
- mkoubaa 2y agoTangent: 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 2y agoIf 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 2y agoIt'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
- pjmlp 2y agoBecause C++, just like C, Ada, Cobol, Fortran, Modula-2, Pascal is an ISO driven language. Whereas Python language evolution is driven by whatever CPython reference implementation does. Compilers are free to do whatever they want, but then that code isn't portable.
- mkoubaa 2y agoThis is also true about #pragma once But it became a de facto standard at some point. thank you for the clarification. You are 100% right about the general difference. I didn't consider the level of "confidence" python has in directing it's own evolution that I don't detect in the C++ committee
- pklausler 2y ago> Compilers are free to do whatever they want, but then that code isn't portable. At a practical level, this is no different from Fortran, “ISO driven” or not.
- edflsafoiewq 2y agoSo 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 std::println("Center is: {}", getCenter()); std::println(f"Center is: {getCenter()}"); // same thing, no basic_string allocated In exchange we have the following problems // f-strings have unexpected type when using auto or type deduction. // basic_string is expected here, but we get basic_formatted_string. // This is especially bad because basic_formatted_string can contain // dangling references. auto s = f"Center is: {getCenter()}"; // f-strings won't work in places where providing a string currently // works by using implicit conversion. For example, filesystem methods // take paths. Providing a string is okay, since it will be implicitly // converted to a path, but an f-string would require two implicit // conversions, first to a string, then to path. std::filesystem::exists(f"file{n}.dat"); // error, no matching overload There are two other proposals to fix these problems.
- HeliumHydride 2y agoYes. 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.
- SkiFire13 2y agoThis seems pretty similar to Rust's `format_args!` macro, which however avoids these issues by being much more verbose and thus something people are less likely to use like in those examples. It does however have issues due to the abundant use of temporaries, which makes it hard to use when not immediately passed to a function. I wonder if C++'s fstrings have the same issue.
- ajb 2y agoHah. What's interesting about this is that since it doesn't require everything to actually be converted to a string, one can implement things other than just printing. So you could also implement interpretation, eg: pylist = python(f"[ y*{coef} for y in {pylist} if y > {threshold}]")
- amluto 2y agoThis links to a “decays_to” proposal: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3398r0.pdf 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 2y agoIOW 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 2y agostd::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.
- dgfitz 2y agoSomehow 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 2y agoCame 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 2y agoOff 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 2y ago17 is worth it for std::filesystem alone. It also has optional and variant.
- efitz 2y agoWhen 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/++
- andyg_blog 2y agoI 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.
- mixmastamyk 2y agoSo, 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
- eviks 2y agoWhy is it odd to copy a popular and fitting alternative? What's the better one?
- mixmastamyk 2y agoBecause it’s an variant with a wart, only picked from being backed into a corner. I mentioned shell interpolation in my previous comment.
- snthpy 2y agoPRQL contributor here: That is a valid point and something I've also been thinking about lately. I can't speak for the others but in my case the Python string interpolation syntax was the one I was most familiar with, other than bash, so it was just the default. The big idea really is to have string interpolation and the syntax is somewhat secondary but we do aim for ergonomics with PRQL so it is a consideration. Since then I've seen more alternatives like `Hello ${var}!` in JS/TS and $"Hello {var}!" in F#. Not sure that there's a clear way to prefer one approach over the others. What would you consider to be factors that would make you prefer one over the others?
- mixmastamyk 2y agoI suppose simplicity and familiarity would be the primary two goals. Then congruence with SQL would be another factor specifically for PRQL.
- 2y ago
- neonsunset 2y agoReinventing C#’s FormattableString and interpolated string handlers :)
- ggm 2y agoI'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.
- bvrmn 2y agoBless compilers able to catch wrong format specifiers.
- orf 2y agoFYI that code sample is broken, it should be `(int(many()),)` Ironic I guess?
- lifthrasiir 2y agoIt's not broken (try it!). Any value is interpreted as an implicit 1-tuple if it's not a tuple nor a dict. A better example would have been `"..." % many()` where `many` returns a tuple or dict.
- alterom 2y agoYeah, I really missed ubiquitous C preprocessor macros in C++, so let's bring them back, but now inside string literals. Sweet. Seriously, I just keep being amazed that people are running with the idea of having a full-blown untyped and unchecked formatting mini language (that's what libfmt, which became C++20 format, literally calls it) inside string literals — i.e., the part of the source code that you're specifically telling the compiler to not treat as code. Think about it for a minute.
- HeliumHydride 2y agoFormat strings in C++ are checked completely at compile time. There are no hacks or compiler intrinsics involved (like what C does for printf to verify format strings).
- jandrewrogers 2y agoEh? C++20 format is checked at compile-time. This has been possible ever since string literals became constant expressions. These features are within the standard compile-time capabilities. People have done impressive compile-time parsing and codegen using it.
- qalmakka 2y agoSo basically doing the same at runtime for the last 55 years was somewhat OK?
- feverzsj 2y agoLike the compile time isn't long enough. They should just let the compiler do the job like Wformat=2 and skip any preprocess and constexpr function.
- pjmlp 2y agoIt is fast enough when using precompiled headers, binary libraries, and best of all C++20 modules with C++23 modularised standard library. Could it be better? Most likely.
- forrestthewoods 2y agoC++ modules do not exist in practice and probably never will.
- pjmlp 2y agoOffice team apparently is of another opinion, as Vulkan folks, and a few other early adopters. You could just as well say that anything beyond C89 doesn't exist, given its prevalence in some circles.
- forrestthewoods 2y agoYes. There are a very small handful of early adopters in the year 2025 for a feature ostensibly added in C++20. So, like I said, modules don’t exist in practice and I’d be shocked if in 2030 modules were considered normal. C++11 was pretty game changing. C++14 and C++17 only took a few years to reach widespread adoption. It’s very safe to require C++17 today. C++20 was a little slower and because of the modules fuckup it’s a bit inconsistent. But it’s largely fine to use. C++23 probably needs another year or two. But also C++20 and beyond haven’t added much that’s worth upgrading for.
- pjmlp 2y agoLike I said, it is a matter of point of view, and yes such is the karma of ISO driven languages with multiple implementations, when one cares about cross platform code. There are many folks that don't care though, for them it is "one platform, one compiler, language standard is whatever my compiler allows me to do, including extensions". I am also quite bullish on the opinion that eventually, C++26 might be the last standard, not that WG21 will stop working on new ones, rather that is what many will care about when using C++ in a polyglot environment, as it is already the case in mobile OS platforms, the two major desktop platforms and distributed computing (CNCF project landscape). Why C++26 and not earlier? Reflection.
- otteromkram 2y agoDecline based on usage of pascalCase in the first example. How did that even happen?
- 3836293648 2y agoThat isn't even PascalCase. This is camelCase.
- Laiho 2y agoIs it a coincidence that all these quality life things start to pop up after C++ is facing real competition for the first time? Seems a bit odd to add print after using std::out for 30 years.
- Capricorn2481 2y agoWhat is this referring to? I would imagine whatever you consider recent competition is actually not that recent.
- vanviegen 2y agoOn the time scale of c++, rust is very recent. :-)
- tialaramex 2y agoNerd alt-history story: What if Graydon decides he should attend WG21 and so instead of Rust what we get is a decade of attempts to fix C++ and reform the process, followed by burn out?
- hnlmorg 2y agoThen we'd be supporting a different language that shares the same or similar ideals as Rust. Whether that's something already in existence or something entirely new. Rust isn't really that unique, there are plenty of other safe languages out there. And if Graydon was alone in wanting something like Rust then Rust wouldn't have grown in popularity like it has. Rust exists because enough people thought there was a need for Rust to exist. So if that wasn't Graydon with Rust, then it would have been someone else with something else. This isn't meant to take anything away from Graydon nor Rust. Just saying that innovations seldom happen in silos. They're usually a result of teams of people lusting for change.
- pjmlp 2y agoRust was helped by being a Mozilla language, and some of the personalities it had around it. The big plus of the language was proving that Cyclone ideas to improve C, from AT&T research project were sound and could be made mainstream. And now other languages are building on it as well, that is why Swift, Chapel, Haskell, OCaml, D are also having a go at a mix of linear types, affine types and effects. However many folks credit Rust for type system features that are actually available in any ML derived language, or Ada/SPARK, so it isn't as if knowledge is that well spread.
- biofox 2y agoMaking any changes to the core language is a sensitive thing as it inevitably imposes new demands on compilers, a learning curve for all users of the language, and risks breaking compatibility and introducing unforeseen issues that will need to be fixed with future changes to the language. Personally, I'd much prefer a smaller and more stable language.
- eviks 2y agoLeaving curve can decrease as a result of better design, same re. the chance of those unforeseen issues (and it can even decrease the chance of existing bugs popping up)
- boxed 2y agoJesus. This is such a bad idea. Don't repeat the mistakes of Python. Look at what Swift does and make a SANE system ffs.
- hikarudo 2y agoSincere question: what's wrong with Python f-strings?
- boxed 2y agoThree people in a row and not one of you guys checked out Swift strings before commenting, thus making exactly the mistake I complained about. Look up Swift strings. Python has 4 types of string literals. Swift has 1. And they are BETTER and more powerful. Cleaner.
- edflsafoiewq 2y agoSwift has "normal strings", #"raw strings"#, and """ for multiline strings. For interpolation, it uses "The answer is \(1+2)", but in a raw string you need #"The answer is \#(1+2)"#. So compared to Python, string interpolation is always "on" and doesn't need an f-prefix. Because it uses the string escaping syntax, it doesn't have to take over a regular character like {, which requires {{ escaping.
- Mond_ 2y agoIs this supposed to be better than python? "Every string is an f-string, make sure you don't accidentally miss some interpolation." sounds like a step down, not like an improvement to me!
- edflsafoiewq 2y agoWhat do you mean by "miss some interpolation"? There are two errors you could make in Python. Accidentally using {} in a normal string where you wanted interpolation, and accidentally using {} in an f-string where you wanted literal {}. I definitely do the former a lot.
- nickysielicki 2y agoReflection is going to change things so much.
- vitaut 2y agoUnfortunately reflection as currently proposed is insufficient to implement string interpolation.
- beeforpork 2y agoIt would be nice to take care to allow the use of GNU gettext() or any other convenient translation tool. Recap: _("foo %s") macroexpands to gettext("foo %s"), then "foo %s" is extracted to a lexicon of strings by an external tool, which can be translated and compiled into .po files, which are loaded at runtime so gettext() can use a translated string based on $LC_MESSAGES. (And there is also _N(..) for correct plural handling.) To do this with f-strings, _(f"foo {name()}") (which is a bit ugly...) needs to translate to make_formatted_string(_("foo {}"), name()) -- note that the _(...) needs to be called before calling make_formatted_string, to be able to return a translated string. I would wish for a proposal for f-strings to consider translating strings, because we live in a world with many languages. And maybe cite gettext as a convenient method, and think about what could be done. Or point to a better tool. Or state: 'in that case, f-strings cannot be used'.
- eqvinox 2y agoI guess _ could be a function that both takes and returns basic_formatted_string? (I.e. not gettext()).
- TheRealPomax 2y agoThe C++ language itself shouldn't be tied to any one specific application or third party tool, though. Just because they exist doesn't mean you are forced to use them, this is one of those cases where f strings don't make a lot of sense. Things with localized labels or text ideally have an id that gets looked up, so you can't do English-based string composition. Every locale gets looked up, no "just pass through the key if locale X", and lookup failures don't "still work", they result in super obvious, user-reportable nonsense.
- vitaut 2y agoThis proposal is not targeting C++26 and there is a new revision of it: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3412r1.pdf https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p34...
- Yoric 2y agoThat's funny. We introduced essentially this features in OCaml Batteries Included ~20 years ago. This brings back memories :)
- robertlagrant 2y agoI'm hoping the next version gets the walrus operator.
- dingxiong 2y agoI am tired of PDFs. They should have a dedicated website for presenting C++ proposals so everyone can comment and discuss. Reading Github issues are more enjoyable than reading PDFs.