Date: Sun, 15 Oct 2023 14:55:23 -0500
On Sat, Oct 14, 2023 at 10:40 PM Hadriel Kaplan <hkaplan_at_[hidden]> wrote:
> > From: Barry Revzin <barry.revzin_at_[hidden]>
>
> > 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.
>
> We could define a new overload such as emulated here:
> https://godbolt.org/z/rd8x6GdaK
>
> But that would change the matching-function `print("foo")` previously
> invoked, and I don't know if that means it's an ABI-break (a non-starter
> obviously).
>
> And to be clear: I'm not proposing such a change would be part of this
> proposal - it would be a separate proposal, if done at all. It just seemed
> silly to me that both print() and println() _require_ the format-string to
> be known at compile-time. I guess that's why vprint_unicode() exists, but
> it's awkward.
>
That one now has a different problem: you're double-formatting the string.
You're successfully preserving the behavior of std::print("X{{}}") as
printing "X{}", but if you try to do something like
std::print(f"Point{{.x={x}, .y={y}}}"), to print the string "Point{.x=1,
.y=2}", that would now break - since the f-string would evaluate into
something that isn't itself a valid format string because of the {}s, so
it'd fail. At runtime.
>
>
> > 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.
>
> Actually, after some more thought, I don't think `format_to()` should
> change to support taking a string anyway. I mean the function _is_ named
> "FORMAT_to()" after all - it's for the purpose of formatting.
>
> You don't ever need to use such a function if you have a resolved string
> already, so you wouldn't use it with an f-string either.
>
> You could just do `*out = F"x={x}";` and be done, no?
>
That's kind of the point - I would really want to use these functions
without having to have a resolved string already. If f-strings were broadly
usable, then I could use them to implement the formatter for Point like
this:
return format_to(ctx.out(), f"Point{{.x={point.x}, .y={point.y}}}");
instead of like this:
return format_to(ctx.out(), "Point{{.x={}, .y={}}}", point.x, point.y);
Like all string interpolation in general, for 1 or 2 arguments maybe isn't
such a big deal, but once you get past there, the f-string pretty quickly
dominates readability so I would like to be able to use it everywhere I
need to do string formatting.
>
> > 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.
>
> That's _ok_. F-strings aren't a replacement for formatting-functions, nor
> even completely for std::format. There are various things that std::format
> can do that an f-string cannot.
>
> Just like in python they weren't a complete replacement for str.format()
> either. And yet they're extremely popular.
>
> F-strings are just a convenience to improve argument-usage visibility, and
> to be less verbose. They're usable in the most common use-cases, which is
> why they're still useful despite limitations.
>
> -hadriel
>
>
> Juniper Public
>
To me, the broadly common use-cases of format are (in no particular order):
- creating a standalone string
- formatting into a buffer
- printing
- logging (which may involve serializing the unformatted arguments, and
formatting in a background thread or even a separate process)
The way you're describing f-strings, as just sugar for a call to
std::format, really only handles the first one there. It's too expensive to
be used in the second or fourth (and some users that do those operations
may not even be able to create a std::string to begin with, so it may not
even be a question of "too expensive" but rather simply impossible) and I
think the only way it would work for printing would be print("{}",
f"x={x}").
I think we can do better than that.
Barry
> > From: Barry Revzin <barry.revzin_at_[hidden]>
>
> > 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.
>
> We could define a new overload such as emulated here:
> https://godbolt.org/z/rd8x6GdaK
>
> But that would change the matching-function `print("foo")` previously
> invoked, and I don't know if that means it's an ABI-break (a non-starter
> obviously).
>
> And to be clear: I'm not proposing such a change would be part of this
> proposal - it would be a separate proposal, if done at all. It just seemed
> silly to me that both print() and println() _require_ the format-string to
> be known at compile-time. I guess that's why vprint_unicode() exists, but
> it's awkward.
>
That one now has a different problem: you're double-formatting the string.
You're successfully preserving the behavior of std::print("X{{}}") as
printing "X{}", but if you try to do something like
std::print(f"Point{{.x={x}, .y={y}}}"), to print the string "Point{.x=1,
.y=2}", that would now break - since the f-string would evaluate into
something that isn't itself a valid format string because of the {}s, so
it'd fail. At runtime.
>
>
> > 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.
>
> Actually, after some more thought, I don't think `format_to()` should
> change to support taking a string anyway. I mean the function _is_ named
> "FORMAT_to()" after all - it's for the purpose of formatting.
>
> You don't ever need to use such a function if you have a resolved string
> already, so you wouldn't use it with an f-string either.
>
> You could just do `*out = F"x={x}";` and be done, no?
>
That's kind of the point - I would really want to use these functions
without having to have a resolved string already. If f-strings were broadly
usable, then I could use them to implement the formatter for Point like
this:
return format_to(ctx.out(), f"Point{{.x={point.x}, .y={point.y}}}");
instead of like this:
return format_to(ctx.out(), "Point{{.x={}, .y={}}}", point.x, point.y);
Like all string interpolation in general, for 1 or 2 arguments maybe isn't
such a big deal, but once you get past there, the f-string pretty quickly
dominates readability so I would like to be able to use it everywhere I
need to do string formatting.
>
> > 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.
>
> That's _ok_. F-strings aren't a replacement for formatting-functions, nor
> even completely for std::format. There are various things that std::format
> can do that an f-string cannot.
>
> Just like in python they weren't a complete replacement for str.format()
> either. And yet they're extremely popular.
>
> F-strings are just a convenience to improve argument-usage visibility, and
> to be less verbose. They're usable in the most common use-cases, which is
> why they're still useful despite limitations.
>
> -hadriel
>
>
> Juniper Public
>
To me, the broadly common use-cases of format are (in no particular order):
- creating a standalone string
- formatting into a buffer
- printing
- logging (which may involve serializing the unformatted arguments, and
formatting in a background thread or even a separate process)
The way you're describing f-strings, as just sugar for a call to
std::format, really only handles the first one there. It's too expensive to
be used in the second or fourth (and some users that do those operations
may not even be able to create a std::string to begin with, so it may not
even be a question of "too expensive" but rather simply impossible) and I
think the only way it would work for printing would be print("{}",
f"x={x}").
I think we can do better than that.
Barry
Received on 2023-10-15 19:55:37