How do I use std::formatter directly?
Asked Answered
H

1

12

Let's say I want to format a single object directly using std::formatter, bypassing std::format. How do I do that?

According to Formatter, I need to call .format(value, format_ctx), where format_ctx is a std::format_context or std::basic_format_context<...>. But how do I construct this context?

The standard doesn't seem to provide a way to construct one. And looking at libstdc++ sources, the member variables of basic_format_context are all private, there's no non-default constructor, and no way to set them without being a friend.

Does this mean that std::formatter is impossible to use manually by design?


Why am I doing this?

I want to format a value using the "debug format" ("{?:}") if it's supported, falling back to the regular "{}".

The way to check for support seems to be requires(std::formatter<T> f){f.set_debug_format();}, and I figured that if I'm already interacting with the formatter directly, I might as well use only the formatter itself.

Hartsfield answered 16/11, 2023 at 5:47 Comment(34)
This is indeed strange considering that fmt does have the public constructors: github.com/fmtlib/fmt/blob/…Ileana
@WeijunZhou is it possible that implementation is incomplete? Afaik there is no full c++20 one yetGuise
@Swift-FridayPie The standard itself doesn't document public constructors here, it seems.Hartsfield
Dose this C++23 concept help: en.cppreference.com/w/cpp/utility/format/formattableEthno
@Ethno I don't see how it helps.Hartsfield
@Hartsfield last paragraph.Ethno
@Ethno What about it? The unspecified default iterator type means I can't really use std::format_context, alright, but std::basic_format_context lets you specify a custom iterator, so I thought I could do that.Hartsfield
@Hartsfield for the iterator you need to sparately check std::output_iterator<iter, const CharT&>. It is unlikely that a type specializes for a specific iterator, but very likely that it only specializes for char(default). Do you want to specialize on iterator?Ethno
Finally you call it=std::format_to(it,fmt,...);Ethno
@Ethno "Do you want to specialize on iterator?" No, I'm asking how to call std::formatter directly. I don't care what iterator it uses internally as long as it works, I'd probably use it with std::back_inserter. The only reason why I'm talking about custom iterators is because the default iterator type used by std::format_context is unspecified, so I can't use it directly, so I need a different type if I want to construct basic_format_context manually.Hartsfield
"Does this mean that std::formatter is impossible to use manually by design?" It is abundantly clear that std::formatter is impossible to use manually (portably at least). The standard just doesn't give you any way to construct one. The only remaining question is whether it is by design. I don't know for sure because I don't have relevant design documents, but from the looks of it, it seems so.Reception
@n.m.couldbeanAI std::formatter itself is required to be defaut-constructible.Hartsfield
@Hartsfield Correction, "any way to construct one" => "any way to construct arguments to formatter ::format". And it is required to be defaut-constructible because the library should be able to default-construct it. The programmer is not supposed to.Reception
std::format_context doesn't need any magical iterator type. The iterator comes from first argument to std::format_to which is an instance of basic_string::iterator identified by basic_string_view instance taken from basic_format_string::get which in turn is identical to the basic_string_view that captures fmt. All you need to check about iterator is concept std::output_iterator<iter, const CharT&>.Ethno
@Ethno I'm asking how to use std::formatter without std::format (and without any similar functions, such as std::format_to).Hartsfield
I don't understand why you need to touch the context. Context is obviously supposed to be reserved for library internals, not for user to tamper with. It os created from the upperhand range which maybe created from a stream. You need to create a costume range class and let format_to create the context. If you want to create a temporary context for a range type, then good luck with that: youtu.be/EQELdyecZlU?feature=sharedEthno
@Ethno practically speaking, current fmt group is least usable\flexible thing that exsts in the realm of format output. It's fastest to use, slowest to runtime one. Funny thing.. so hated printfs\scanfs still stay the most flexible one and libraries with similar capabilities behave either in similar way or use regex flavors. fmt was clearly designed by someone coming from front-end development.Guise
@Swift-FridayPie printf has always been an will always be a big nono in C++ . C variadic functions are just relics from the past. scanf is even worse. {fmt} is created by a well rounded C++ expert. I might not agree with the design, but it's by far the best we've got. strong type-checking, compact syntax and readability are it's advantages; the only major problem is lack of foresight for genericness and scale. printf can't even provide minimum extensibility. If I were to design fmt, I'd start with tuple as the primary formatable type. I am no fan of SWIFT; neither Taylor, nor Apple's.Ethno
@Swift-FridayPie "slowest to runtime" Do you have any source for that? Libfmt's readme has some benchmarks, and they look quite good.Hartsfield
@Ethno The problem arises where you need a constant stack and linear predicatable time footprint. None of C++ versions have either (and boost and QT's .arg are the worst in stack case, fmt can compete is libc IF it uses same format line foor every output on x64.. but looses badly to libc and libc++ on some ARMs and VLIW). henyour stack is only 0.5-1.5 Mb for a process and your time deviation should be, let say, +/- 3 ms, that's these are a no-no. printf by definition got linear stack footprint and matching time with two forks, by design.Guise
@Hartsfield yeah, though they crafted benchmark against ostream version . Thereis even a way make printf slower than C++ if you use particular pattern. Also really dependant on arch. My target arch is a 96bit VLIW CPU, stand-alone {fmt} is the slowest, and we don't have a C++20 capable compiler yet (newest compiler is C++14)Guise
@Swift-FridayPie I am sure you're mistakeing vformat with format. format prcesses the fmt string at compile-time; So combined with fputs or fwrite, it should outperform printf. Because printf must interpret fmt at runtime.Ethno
@Ethno most of our use cases ARE run-time. among other things it's not permitted to spread output through compilation units. One can slightly cheat it by making some kind of intermediate dispatching.Guise
@Swift-FridayPie if you need to craft the fmt string at runtime, you have both design and readability problem. Even in C, it's very rare that the fmt string is not a core constant expression.Ethno
@Ethno If you're localizing the messages, all the strings will be created at runtime.Hartsfield
@Hartsfield and if someone says "you can separate formatted code from localized", they will be wrong(I met some Canadian dev like that once) they will be wrong because even numbers are displayed differently depending on locale or context.Guise
@Swift-FridayPie what do you mean by localizing messeges? I fail to see why the fmt string(first argument to format) should not be a core constant expression.Ethno
I guess we have different understanding about what the term fmt string means.Ethno
@Ethno Means that content of string depends on current locale setting of software or OS or even program's settigs or current context (write into particular width?). And sometimes even more dynamically than that. In { arg-id (optional) : format-spec } the format-spec wouldnt be a core constantGuise
For further clarification, I am referring to the first parameter sent function format or printf. Your seem to be confusing it with return value from format.Ethno
@Ethno Say you're writing a game, and you have fmt::format("You have {} HP", player_health) displayed somewhere. player_health is only known at runtime, so the format string needs to be localized, not the return value. You can't localize "You have " and " HP" separately, because that's 1. not convenient, and 2. not feasible if there's >1 {}, since they might need to be reordered in different locales.Hartsfield
@Hartsfield On technicality you can use "{} {} {}", YouHaveString, HPValue, HpString. Only that's unworkable because with different language order on number of arguments will change and that is compile-time thing. {} in other context may contain width specifier because we trying to format all numbers based on.. e.g. Excel table comun's width. So even that becomes flexible.Guise
@Swift-FridayPie Aha! So you basically need a diversity of fmt strings to choose from. Indeed those fmt string need to be built from core constant expressions. The choice(e.g. index) is however made at runtime. All the information about std::wformat_string is provided at compile-time and can be saved as part of type information. I can show you how to avoid vformat and use format instead. But that requires another discussion thread.Ethno
Not always they can be a part of code, because localization is done for already complete (and copiled) program by certified specialist. Not by devs, though in real life it includes a lot of back and forth because of some issues with localization support in code)Guise
G
6

The use case you're describing, where you never actually call std::(v)format(_to) and just use the formatter directly, isn't supported. You should just call std::format with the format string that you want to use, i.e., {} or {:?}.

And even if you could bypass std::format, you would just be creating work for yourself. You'd have to call std::formatter<T>::parse and std::formatter<T>:format manually (since the former sets up state that is used by the latter). And you'd have to manually set up the contents of the std::basic_format_parse_context and std::basic_format_context objects, assuming that they supported that in the first place.

On the other hand, a formatter can invoke another formatter, by passing down the std::basic_format_parse_context and later the std::basic_format_context that were passed to it by the library. That's basically how you'd implement a range formatter (if it weren't for the fact that the standard already provides one).

Girardo answered 17/11, 2023 at 1:3 Comment(1)
creating work for yourself that's an argument for front-end developer, where "less code is better". We're still in realm of system software design. But yeah, fmt is designed as a front-end tool and there is no way around it except "do not use it" otherwise.Guise

© 2022 - 2024 — McMap. All rights reserved.