On Wed, Dec 28, 2022 at 10:12 AM Jason McKesson via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
On Wed, Dec 28, 2022 at 3:17 AM Henry Miller via Std-Proposals
<std-proposals@lists.isocpp.org> wrote:
> Your second objection to format is a basic requirement of all string systems, and so any string library, in any programming language that doesn't feature that is wrong. It is critical that all programmers get  used to the idea their strings may need to be translated to some foreign language.  Different languages feature different word order and so the order the variables appear in the output must be flexible.
> While is seems likely that any string that gets translated will use the strings provided by their UI toolkit and not std, we should still encourage the mindset in young programmers that variables should not be in the same order as the output.

I should point out that this notion also speaks to the intended
durability of programs written in a certain language.

Python code is probably the most prolific user of interpolated
strings. But there's a lot of Python code that is written for
scripting purposes. In these cases, most formatted strings aren't UI
strings that would ever need translating.

One of {fmt}'s main advantages over iostreams is the ability to do translation well. But not every domain requires translation. I've never worked in a C++ domain that does - but that doesn't mean my C++ code is written for scripting purposes or is ephemeral. I just don't need translation. 

But here's the thing. No matter how "script friendly" C++ becomes, I'm
never going to use C++ for a scripting-style task. Python exists, and
is way more trivial to use, so I will use it for those tasks. C++ is
meant for more durable code, stuff that you build and maintain, so the
need for translation increases.

Indeed, which is precisely why interpolated strings are such a useful tool for C++: they are more durable. The main disadvantage with {fmt} as compared to iostreams is that all your arguments just go at the end - so if you're formatting 3, 4, 6, 10 arguments, it's just hard to ensure that you actually put them in the correct order - whereas with iostreams this is very obvious. Interpolating strings lets you stick in the arguments in-place, making the code more obviously correct by construction.

Basically, consider this: in Python, interpolated strings are a
language feature, while formatted strings are a standard library
feature. That says something about the expectations of Python.
Interpolated strings are not inherently bad practice, but they are bad
practices within certain contexts.

Contexts that C++ widely gets used in.

So however nifty they are, they also push C++ programmers to think of
their code as ephemeral. Which it almost never is.

I'm not sure what interpolated strings have anything to do with ephemerality. Rust has interpolated literals too, by the way. 

Anyway, back to the OP. iostreams have no need for f-strings. This (and, btw, you forgot the f everywhere):

std::cout << f"threshold is {threshold}, time is now {time()}" << std::endl;  

is shorter than this, by 9 characters:

std::cout << "threshold is " << threshold << ", time is now " << time() << std::endl;  

But otherwise it's not really that big a difference and it's not really worth it to support. Sure it's more readable in the sense that there are fewer <<s, but... meh. 

On the other hand this:

fmt::print(f"threshold is {threshold}, time is now {time()}\n");

is substantially better (though not much shorter) than this:

fmt::print("threshold is {}, time is now {}\n", threshold, time());

I think this facility is really only worth supporting for format. Plus, I think it's exceedingly hard to come up with a way for it to work for both format and iostreams to begin with, since their respective syntaxes are so different. So there's more value in supporting just the better one. 

Other notes:
  • the expressions in {} need not be rvalues, threshold is an lvalue. They're just arbitrary expressions
  • interpolated literals need to handle specifiers, so print(f"value in hex is {value:#08x}") needs to evaluate as print("value in hex is {:#08x}", value)
  • specifiers themselves can also have expressions. For instance in Rust println!("Total score is [{total_score:>width$}]") is printing the value of total_score with a width of width. In Python, this is f"Total score is {total_score:>{width}}"
  • brace escaping in both Python and Rust is {{ not \{