Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mixing named and automatically indexed placeholders #4069

Closed
rbrich opened this issue Jul 12, 2024 · 3 comments
Closed

Mixing named and automatically indexed placeholders #4069

rbrich opened this issue Jul 12, 2024 · 3 comments
Labels

Comments

@rbrich
Copy link
Contributor

rbrich commented Jul 12, 2024

Consider allowing to mix the named and automatically indexed placeholders:

fmt::format("{}",  1, fmt::arg("a", 2), fmt::arg("b", 3));  // ok
fmt::format("{} {a}",  1, fmt::arg("a", 2), fmt::arg("b", 3));  // ok
fmt::format("{} {b} {a}",  1, fmt::arg("a", 2), fmt::arg("b", 3));  // ok
fmt::format("{b} {a} {}",  1, fmt::arg("a", 2), fmt::arg("b", 3));  // error
fmt::format("{a} {} {b}",  1, fmt::arg("a", 2), fmt::arg("b", 3));  // error

I think all these are valid usages and should be supported. They all worked before 11.0.0 and the last two stopped working after that release.

On the other hand, I don't really care if this works or not:

fmt::format("{} {a} {b}",  fmt::arg("a", 2), fmt::arg("b", 3), 1);  // ok, {} references fmt::arg("a", 2)

In 11.0.0 (after #3817), the named arg placeholders are considered indexed and the automatic indexing stops working on the first indexed or named placeholder. The automatic placeholders refer to the fmt::format args in the order they appear, regardless if they are named or not.

The use case

The reason to support this is to allow layering of APIs - a higher level function provides fmt-like interface, but internally adds some named arguments for the user to reference:

print_with_errmsg("Error {m} in {}, something);
print_with_attrs("{bold}{fg:red}{}{normal}", bold_red_text);

These functions internally add some named args at the end, so they don't affect the indexes of user-visible args. For this use case, it would even make sense to forbid referencing the named args by indexes or by automatic indexing, essentially forcing them to be passed at the end, after positional args.

You can of course force users of such APIs to use only indexed args and never the automatic placeholders, but it's not really nice.

I acknowledge that this is kind of niche usage, but there is not many ways around it, if you want to provide the optional value(s) for user to reference, and a nice API.

Note that both Python and Rust support this:

"{name} {}".format(1, name=2)    # => '2 1'
format!("{name} {}", 1, name = 2);          // => "2 1"

And both forbid passing positional args after named args (i.e. mixing them in the arguments to format function). On the other hand, Rust forbids unused named arguments, which would also break the above use case. Python is fine.

@vitaut
Copy link
Contributor

vitaut commented Jul 13, 2024

Thanks for the suggestion. It is an interesting idea to use named vs unnamed arguments in such way but I don't think it's worth relaxing the checks for this. A potentially better approach would be formatting arguments from different sources separately.

@rbrich
Copy link
Contributor Author

rbrich commented Jul 14, 2024

A potentially better approach would be formatting arguments from different sources separately.

I don't understand how this would be done or how it would help with the use case. You cannot call fmt multiple times to process format string incrementally, consuming just some args at the time. That causes a format error. The only possible workaround I see is to write a custom parser to preprocess the string - e.g. first find and replace the {m}, then delegate to fmt. This unfortunately means that the compile-time checking cannot be used.

So there doesn't seem to be any good solution for the use case without direct support in fmt itself.

@vitaut
Copy link
Contributor

vitaut commented Jul 18, 2024

You can do it by escaping the placeholders that need to be processed in the second stage. (Pre)processing of the format string is an option too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants