C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Supporting f-strings in C++

From: Barry Revzin <barry.revzin_at_[hidden]>
Date: Sat, 14 Oct 2023 20:00:31 -0500
On Sat, Oct 14, 2023 at 3:55 PM Hadriel Kaplan <hkaplan_at_[hidden]> wrote:

> > From: Barry Revzin <barry.revzin_at_[hidden]>
>
> > I think this approach is kind of a non-starter. We can't have f"x={x}"
> just mean std::format("x={}", x) for an important reason.
>
> > This means that std::print(f"x={x}") doesn't and can't work - which is
> the sort of thing that seems important to support - having to write
> std::print("{}", f"x={x}") is... less than ideal.
>
> > More generally, the issue is that there are a lot of uses of the format
> API that are not literally std::format(). In the standard we have
> std::print(), std::format_to(), etc. It would be really nice if those
> worked with f-strings as well.
>
> I explicitly covered that topic in section 5 of the draft...
>
> We only need to add another overload to std::print(), std::println(), and
> format_to() - one that accepts a std::string_view or std::string - to make
> these work:
>
> std::print(F"hello C++{20 + 6}");
> std::println(F"hello C++{13 * 2}");
> std::format_to(std::back_inserter(buffer), F"hello C++{26}");
>

You can't do that though, since you can already call std::print, etc., with
just a string literal - and the meaning of std::print with just a format
string is distinct from the meaning of what you're suggesting calling
std::print with a std::string would be. For instance, std::print("X{{}}")
is a valid call today - which prints "X{}" - because we always interpret
the first argument as a format string. If we add this overload, it would
suddenly print "X{{}}", because we would interpret the argument as just a
string.

Incidentally, Rust tried to do this for a while where panic!("{}", 1) would
panic with the message "1" but panic!("{}") would not be interpreted as a
format string due to the lack of arguments - the latter would have panicked
with the message "{}". But Rust 2021 fixed that by making panic!("{}")
ill-formed (since it's a format string expecting one argument, which isn't
provided). I'm not saying we should avoid making this change from one to
the other simply because Rust just made the change in the opposite
direction, but it is a data point.


>
> The first two functions might not be as efficient as using a separate
> format_string and args, because an intermediate std::string is created
> which might be avoidable otherwise.
>
> But they're already writing to stdout, so an extra temporary string seems
> rather meaningless? At least I think it's reasonable tradeoff for
> convenience.
>

This isn't a good argument though, because while std::print does write to
stdout, not all uses of the format API do. format_to() doesn't. Not all
uses of the format API are even formatting synchronously - some simply do
type-checking in the front end and serialize their arguments to be
formatted later. For such uses, an extra temporary string is a complete
non-starter.


>
> Likewise, this should work without spdlog being changed at all:
>
> spdlog::info(f"x={x}");
>
> ...because *anything* that accepts a std::string rvalue should work -
> because ultimately the f-string resolves to the std::string returned by
> std::format().
>

TIL apparently spdlog makes the mistake that I describe above, where
spdlog::info("{}") actually works and logs "{}" (instead of being
ill-formed) while spdlog::info("{}", 1) logs "1". I consider that a design
mistake, and definitely not one the standard library should adopt.

It's not even documented behavior, spdlog just says it uses fmt. But
fmt::print("{}") isn't valid.


>
> You can almost ignore the fact it uses std::format(), and instead just
> think of an f-string as a std::string&&. Or perhaps like a new
> std::to_string(fmt) function. The fact it uses std::format() to create the
> string is arguably an implementation detail, in some sense.
>
> ---
>
> As an aside: it's actually odd to me that std::print/println do NOT
> already accept a single std::string_view arg today. They do if the
> string/string_view is constexpr (via implicit conversion), but not a
> runtime one. I mean I know why it's happening, but it seems wrong to me -
> an unexpected surprise as a user, and an unnecessary restriction for those
> functions.
>
> ---
>
> Thanks for the review!
>
> -hadriel
>
>
>
> Juniper Public
>

Received on 2023-10-15 01:00:44