C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Proposal to add f-strings to C++ as an improvement of string formatting

From: Jason McKesson <jmckesson_at_[hidden]>
Date: Sat, 31 Dec 2022 18:13:48 -0500
OK, so let's start an examination of the practical aspects of this
concept. That should begin with P1819 (https://wg21.link/P1819), which
was reviewed by EWGI in 2022
(https://github.com/cplusplus/papers/issues/588). So apparently, that
paper is still active.

What we can tell from just the review is that string interpolation
seems to be something that people would like to see happen in some
form. There were several aspects of that proposal that didn't get
consensus to move forward: the general approach of using a lambda as
well as lazy evaluation. But the idea of having more arbitrary
expressions in the string did get consensus.

So let's start there: what is the result of an interpolated string?
Presumably, we want this to "just work": `std::print(f"interpolated
{expr + expr2} string")`. But do we want to limit it to *just* that
one standard library function?

The question inside the question is this: who decides what does the
actual *formatting* of the string? P1819's idea was that an
interpolated string becomes a callable object to which you can pass a
function that is given all of the pieces of the string pieces and
expression results. Presumably, a function like `std::print` would
detect such an object and pass its own formatting logic to it. There
would be `operator<<` overloads for it as well to allow for stream
output.

It seems to me that the much easier solution is to just give the
interpolated string an interface that allows the code receiving it to
process the interpolated string. We also need to preserve the types of
the values. So a `tuple`-like interface makes sense.

Now, there are several proposals in flight that make dealing with
tuple-like interfaces much easier. The string interpolation proposal
ought to track those so as to make sure that it can fit into their
paradigms. Even so, a simple `for_each` member function for processing
the strings could be adequate, where it repeatedly calls a given
functor with each element of the string. This would not be a regular
callable, as the expectation is that it is building the return value
either in itself or through a non-`const` reference member.

The elements of the string that are literals should not be of any type
a user can directly create. It should be similar to a `std::span<char
const, N>`, where `N` is the number of characters in it. The
difference being that it isn't a `std::span`; it's a type like
`literal_fragment<char, N>` or something that you cannot directly
create. Imagine if `initializer_list` didn't have a publicly
accessible default constructor. Note that the size is a template
parameter (unlike `initializer_list`) so it can be used in constant
expressions.

So to recap, an interpolation string literal expression results in an
instance of `interpolated_string<Ts...>`, where `Ts...` is a pack
containing the various types in the interpolation, either an
expression or a `literal_fragment<char, N>` instance.

Received on 2022-12-31 23:14:00