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

Questions about formatters for std:: entities #3012

Closed
alexezeder opened this issue Jul 31, 2022 · 3 comments
Closed

Questions about formatters for std:: entities #3012

alexezeder opened this issue Jul 31, 2022 · 3 comments
Labels

Comments

@alexezeder
Copy link
Contributor

alexezeder commented Jul 31, 2022

I was thinking about implementing #2977, it seems pretty straightforward, right? Just add a simple fmt::formatter with write(exception.what()) into fmt/std.h and call it a day. But there is also the formatter for std::variant, it outputs the underlying value as variant(42) with escaping string and char values. The other formatter there, for std::filesystem::path, outputs "/home" without any path prefix, which just mimics operator<< of course.

So, such a long introduction to ask a few questions:

  1. Should a formatter for std::exception have any prefix or escape the what string?
  2. Since I also want to add a formatter for std::optional: should it have optional prefix and also escape the string and char values? Just like the formatter for std::variant does.
  3. Is there any point to add a formatter for std::source_location? Since it doesn't have operator<< provided and it consists of 4 components that can be combined in many different ways. For example, it can output using some default format, but what would it be? For me, it's source.cpp:line:column, but who cares what is preferable for me.
  4. Should we add an option to the std::variant formatter to skip variant prefix and an escaping of string and char values? The same decision should also apply to the std::optional formatter.
  5. Should we add an option to the std::filesystem formatter to get a non-escaped path from it?
  6. Should we add a formatter for std::stacktrace (and its entry) if it's currently not testable on CI?

And here comes the crazy question:

  1. Should we add format specifiers for underlying types of std::optional (easy) and std::variant (dizzy)? It's like with a range formatter IIRC, treat all specifiers after another : as underlying specifiers, so the format string {::x} with std::optional{42} would produce 2a (considering that there is no optional prefix). This idea works fine with std::optional, but for std::variant it works only if the format string has underlying specs for the current type of variant.
@alexezeder alexezeder changed the title Questions about formatters for STD entities Questions about formatters for std:: entities Jul 31, 2022
@vitaut
Copy link
Contributor

vitaut commented Aug 5, 2022

I was thinking about implementing #2977, it seems pretty straightforward, right? Just add a simple fmt::formatter with write(exception.what()) into fmt/std.h and call it a day.

Right.

Should a formatter for std::exception have any prefix or escape the what string?

I don't think we should need to format anything other than the message by default but we should provide an escaped option similarly to strings.

Since I also want to add a formatter for std::optional: should it have optional prefix and also escape the string and char values?

I would recommend checking what other languages do.

Is there any point to add a formatter for std::source_location? Since it doesn't have operator<< provided and it consists of 4 components that can be combined in many different ways. For example, it can output using some default format, but what would it be? For me, it's source.cpp:line:column, but who cares what is preferable for me.

Sure. the source.cpp:line:column sounds reasonable, that's a very common format.

Should we add an option to the std::variant formatter to skip variant prefix and an escaping of string and char values?
Should we add an option to the std::filesystem formatter to get a non-escaped path from it?
Should we add a formatter for std::stacktrace (and its entry) if it's currently not testable on CI?

I don't think we should add anything without compelling use cases.

Should we add format specifiers for underlying types of std::optional (easy) and std::variant (dizzy)?

Again, I would recommend checking what other languages do.

As usual PRs are welcome =).

@vitaut vitaut closed this as completed Aug 5, 2022
@vitaut vitaut added the question label Aug 5, 2022
@alexezeder
Copy link
Contributor Author

alexezeder commented Aug 20, 2022

I would recommend checking what other languages do.

I tried to find similar types in Java, Rust, and C#. Also, we can look at what dynamically typed languages do, Python for example. I can be wrong in some examples here since I'm not a programming languages expert.

Java

Java has Optional type, and documentation for toString() method (the closest to {fmt} formatting) states:

If a value is present the result must include its string representation in the result. Empty and present Optionals must be unambiguously differentiable.

System.out.println(Optional.of("foo").toString());        // outputs Optional[foo]
System.out.println(Optional.ofNullable(null).toString()); // outputs Optional.empty

I haven't found any Variant-like type in Java.

Rust

Variants are represented by enum in Rust, it sounds weird but the documentation for them shows how it works. Optionals are just prepared variant in this case - https://doc.rust-lang.org/std/option/enum.Option.html. But enum is not formattable in Rust, thus this code won't compile:

enum Variant {
    Integer(i64),
    String(String),
}

fn main() {
    println!("{}", Variant::String("foo".to_string()));
    println!("{}", Some(42));
}

C#

C# has Nullable type for Optionals, and it can be formatted this way:

Console.WriteLine("{0}", new int?(42)); // outputs 42
Console.WriteLine("{0}", new int?());   // no output, empty line

Looks like C# also doesn't have Variant-like types.

Python

With Python everything is simple - every variable is Variant, and also Optional. The idea is to output a value for the variable (using __str__() if needed) unless it's empty (has NoneType type), in this case, output None.

Summary

It sounds more reasonable for me to apply the same idea as in Python for std::variant formatting - output just a value without adding some weird prefix, at least to disable this prefix by default and provide it in an opt-in way.

As for std::optional, IMHO the same Pythonic idea should be applied, but the output for empty optional is not so straightforward, should it be None, nullopt, [nullopt], (as in C#), ...? 🤔

@vitaut
Copy link
Contributor

vitaut commented Aug 24, 2022

Thanks for researching this. Some thoughts:

  1. I think this is a good goal and we should do the same:

Empty and present Optionals must be unambiguously differentiable.

  1. std::variant may have an empty state (monostate) so we should do the same as for optional and distinguish it from non-empty states. We already try to do this to some extent with empty represented as variant(monostate) but we should also escape the stored value by default as with ranges to preserve outer structure.

  2. We could provide a format specifier to print just the stored value without wrapping in variant(...) / optional(...).

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