26

In Python it's possible to format strings conveniently using f-strings:

a = 42
print(f"a = {a}") # prints "a = 42"

Is it possible to do something like this in C++ at compile time, or is format("a = {}", a); the closest thing to f-strings?

EDIT: I'm not saying that that these formatting methods should work at runtime. Can't compiler just look up variable visible in current scope by it's name at compile time? Like, if it encounters string f"foo = {foo}" it could just substitute this string to std::format("foo = {}", foo) in code

Eladtopaz
  • 1,036
  • 1
  • 6
  • 21
Bamberghh
  • 429
  • 1
  • 5
  • 8
  • 1
    C++ doesn't have the reflection capabilities to make this possible. Once compiled the `a` variable is gone, it is, at best, a value. In many cases it goes away entirely. – tadman Jul 07 '20 at 17:59
  • 2
    I upvoted the question because it would be so lovely, but C++ is unlikely to get that. So far the least-unhappy I get with C++ output formatting is with the fmt library: https://github.com/fmtlib/fmt – markgalassi Apr 06 '21 at 21:04

6 Answers6

15

C++ doesn't have reflection. As a result, it is impossible to offer a reference to a variable by its name.

But as @NicolBolas points out, even if reflection was present, there would be more needed. Not only would it require huge language support (telling the called function what the variables were in the calling scope), it would also prevent tons of optimization. The compiler could not remove any variable, as the format string (when not known at compile time) could end up referring to any variable in-scope.

So, no. std::format is the closest.

Jeffrey
  • 11,063
  • 1
  • 21
  • 42
  • 2
    Reflection would only be the *beginning* of what is needed to make this work. – Nicol Bolas Jul 07 '20 at 18:01
  • yeah, good point. I'll try to make the answer clearer. – Jeffrey Jul 07 '20 at 18:06
  • 4
    I'm not saying that that these formatting methods should work at runtime. Can't compiler just look up variable visible in current scope by it's name at compile time? Like, if it encounters string `f"foo = {foo}"` it could just substitute this string to `std::format("foo = {}", foo)` in code – Bamberghh Jul 07 '20 at 18:46
  • We had so much ~fun~ pain with preprocessor in C++, there's no way such substitution would be approved by the committee (well, I hope) – Jeffrey Jul 07 '20 at 18:58
  • Sorry, I don't understand why Bamberghh's suggestion is not a trivial solution for this to be available in compile-time. The two syntaxes are equivalent in a 1:1 relationship, aren't they? If they are, I don't see the complication. Please explain. Why can't this just be syntactic-sugar for `std::format`? – Gulzar Nov 30 '21 at 13:46
  • @Gulzar: It can't be because C++ doesn't work that way. You would have to change the *language* to make that happen. There is no way to convert text into a C++ identifier. And even if you could do so, there's no way for one function to reach out into the scope of its caller and access variables there. – Nicol Bolas Nov 30 '21 at 14:22
  • 1
    @NicolBolas In `f"foo = {foo}"`, the second `foo` could be understood at compile time as a token, rather than as text. Maybe with slightly different syntax, but surely it is possible. I don't understand why the second `foo` should be refered to as text, rather than as a variable, just like it is with `std::format("foo = {}", foo)`? – Gulzar Nov 30 '21 at 14:30
6

C++ has no f-string mechanism. String interpolation is not possible in the language as it stands. Even with compile-time function calls, you would also need to have such a function "reach out" into the caller's scope to retrieve the contents of those variables and format them appropriately. That would mean that the behavior of a function-based string interpolation mechanism would have to be different, not just based on what string it takes, but based on where it gets called.

That's not a thing C++ really allows.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • 1
    Why is this different from the `std::format("foo = {}", foo)` case? – Gulzar Nov 30 '21 at 13:48
  • 1
    @Gulzar: Because the *compiler* isn't reaching out of the string, finding a variable named "foo", and inserting it into the command. If you want to pass in `foo`, then `foo` has to explicitly be one of the variables in the function call. That function call works like any other C++ function call; no compiler magic is needed. – Nicol Bolas Nov 30 '21 at 14:19
  • The magic I want is to write `printf(f"foo={foo}")` and for the compiler to understand it as `printf(std::format("foo = {}", foo))`. Is this doable? Is it bad? If it is bad, please explain why? – Gulzar Nov 30 '21 at 14:26
  • @Gulzar: I know what you want. But it would require changes to the language. It cannot be done in C++ as it stands. And the OP was asking specifically about what could be done in C++ to make it work, not whether a language change could make it work. – Nicol Bolas Nov 30 '21 at 14:29
5

There's no good way do that.

With some heavy macros you could achieve syntax like F(a = ARG(a)) (where F and ARG are macros), and it will kind of work, but with major limitations (see below).

Here's an example implementation relying on libfmt:

#define F(...) F_LOW( (str, __VA_ARGS__ ) )
#define ARG(...) )(expr, __VA_ARGS__ )(str,

#define F_LOW(...) ::fmt::format(F_END(F_STR_LOOP_A __VA_ARGS__) F_END(F_ARG_LOOP_A __VA_ARGS__))

#define F_END(...) F_END_(__VA_ARGS__)
#define F_END_(...) __VA_ARGS__##_END

#define F_CAT(a, b) F_CAT_(a, b)
#define F_CAT_(a, b) a##b

#define F_STR_LOOP_A(...) F_STR_LOOP_BODY(__VA_ARGS__) F_STR_LOOP_B
#define F_STR_LOOP_B(...) F_STR_LOOP_BODY(__VA_ARGS__) F_STR_LOOP_A
#define F_STR_LOOP_A_END
#define F_STR_LOOP_B_END
#define F_STR_LOOP_BODY(tag, ...) F_CAT(F_STR_LOOP_BODY_, tag)(__VA_ARGS__)
#define F_STR_LOOP_BODY_str(...) #__VA_ARGS__
#define F_STR_LOOP_BODY_expr(...) "{}"

#define F_ARG_LOOP_A(...) F_ARG_LOOP_BODY(__VA_ARGS__) F_ARG_LOOP_B
#define F_ARG_LOOP_B(...) F_ARG_LOOP_BODY(__VA_ARGS__) F_ARG_LOOP_A
#define F_ARG_LOOP_A_END
#define F_ARG_LOOP_B_END
#define F_ARG_LOOP_BODY(tag, ...) F_CAT(F_ARG_LOOP_BODY_, tag)(__VA_ARGS__)
#define F_ARG_LOOP_BODY_str(...)
#define F_ARG_LOOP_BODY_expr(...) , __VA_ARGS__

For example, with those macros F(a=ARG(a), b=ARG(b)) will be equivalent to pythonic f"a={a}, b={b}", and will expand to

::fmt::format("a=" "{}" ", b=" "{}" "", a, b)

which is equivalent to

::fmt::format("a={}, b={}", a, b)

Looks good so far? It can even handle escape sequences (somehow)!

And here are the mentioned major drawbacks:

  • Any sequence of whitespace characters is replaced with a single space.

    • Additionally, any leading and trailing whitespace, and any whitespace surrounding ARG is removed.
  • Any macros in the string are expanded.

  • The string can't contain unbalanced parentheses.

  • ARG(...) can't appear inside of parentheses.

IMO, this makes it unusable for anything except debug output.

Possible workarounds include:

  • Changing the syntax to one of:

    • F("a = " ARG(a)) (I don't like how it looks)
    • F("a = ", (a)) (looks decent, but requires Boost.Preprocessor or equivalent generated macros)
    • F("a = ", ARG a) (same)
    • F("a = ", ()a) (same)
  • Adding a sub-macro (similar to ARG), let's say STR("..."), to include string literals verbatim. This will let you include any whitespace/parentheses/unexpanded macros/... you want.

    Example: F(a = ARG(A)) gives you a =42. If you want a space after =, you do F(a = STR(" ") ARG(A)).

    The syntax is way too obscure, so implementing this is left as an exercise to the reader.

  • ...


Bonus: with the right flags (or #pragmas), most compilers will let you include $ in a macro name. It means you could use $(expr) instead of ARG(expr), if you wanted to...

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
4

No, there is no string interpolation in C++. And the only work in this area I've found is a "very very rough draft of a proposal" Interpolated String Literals from google back in 2015.

We just got the formatting library in C++20 and even that is does not appear to be yet implemented in any standard library implementation (as of July 2020). String interpolation would require language support and the standard committee is always more reluctant to change the language compared to changing the library.

bolov
  • 72,283
  • 15
  • 145
  • 224
4

If you just want to print a string with variables in it, you could always use the ancient printf() function.

char my_name[] = "Michael";
int my_age = 107;
printf("Hello my name is %s. I am %d years old.", my_name, my_age);

sprintf() is a variant which allows you to save the output as a string (char array) and use it again, or send it to a serial port in the case of embedded systems.

I am far more familiar with C than python, so if I am missing something, please do explain.

  • Keep in mind that sprintf is actually unsafe - since it doesn't check the length of the destination buffer, you can overwrite memory that you shouldn't have access to. Better to use [std::snprintf](https://en.cppreference.com/w/cpp/io/c/fprintf) (even though it's technically still unsafe if you pass a larger buffer size than the buffer you're writing to) – Josie Thompson Jan 22 '22 at 21:37
0

I think the simplest method to use something like an "f-string" in c++ is the accepted answer to this other question C++: how to get fprintf results as a std::string w/o sprintf

DonLarry
  • 702
  • 2
  • 11
  • 22