fenbf / cppstories-discussions

4 stars 1 forks source link

2022/custom-stdformat-cpp20/ #91

Open utterances-bot opened 2 years ago

utterances-bot commented 2 years ago

Custom types and std::format from C++20 - C++ Stories

std::format is a large and powerful addition in C++20 that allows us to format text into strings efficiently. It adds Python-style formatting with safety and ease of use. This article will show you how to implement custom formatters that fit into this new std::format architecture. Quick Introduction to std::format   Here’s the Hello World example:

https://www.cppstories.com/2022/custom-stdformat-cpp20/

RobL371 commented 2 years ago

My company has been using fmtlib for a couple of years now. The biggest hurdle has been adoption from our math majors (which we have a lot of, being a simulation program). They really want to put an indicator in the {} that says the type. I can actually understand this for them they want to know the type for certain and then have strong checking.

One big problem with this is that, if you specify a type with a floating point number, like {:f}, there is no way to get the minimum size for guaranteed double-->string-->double exactness. Specifying any options at all gives you a specified precision or format type.

2kaud commented 2 years ago

If the result of std::format is to a stream (which is pretty normal), then you could combine stream insertion with std::format for a custom type without getting involved with custom formatters. eg.

#include <iostream>
#include <format>
#include <iterator>

using fmtstrm = std::ostream_iterator<char>;

struct myStrt {
    std::string s;
    int i {};
    double d {};
};

std::ostream& operator<<(std::ostream& os, const myStrt& strt) {
    std::format_to(fmtstrm(os), "{:-^10}:{:0>10}:{:10.3}", strt.s, strt.i, strt.d);
    return os;
}

int main() {
    myStrt st { "hello", 34, 6.78 };

    std::cout << st << '\n';
}

Which displays: --hello---:0000000034: 6.78

2kaud commented 2 years ago

@RobL371

Can't you do something like:

std::format("{:{}.{}f}", 3.1415926539, 10, 5);

where 10 is the width and 5 is the precision. The width can be a calculated value etc.

RobL371 commented 2 years ago

@2kaud

No, that doesn't work. If you just use {}, then you get a guarantee: The resulting string will be the shortest possible string where a conversion from string back to double exactly matches the original. There is no way to get that guarantee if you use any other options.

See https://godbolt.org/z/sj3Tzn3nd

2kaud commented 2 years ago

Ah yes. I see. If a type is specified, then if precision is not 6 then a precision has to be specified. Only {} doesn't require a precision if 6 is not required. This is due to std:::format using std::to_chars() for the conversion. Only {} calls std::to_chars() without specifying a precision.

You could write your own formatter as per above with a parse() for say type D (d is already used for decimal) that does the same as {}

2kaud commented 2 years ago

As a 'proof of concept', have a look at https://godbolt.org/z/f7M74EoqG

Any specified format specification is ignored - effectively giving {}

RobL371 commented 2 years ago

Yep, that's a good example. The only issue is that we shouldn't need to do that. :)

davidbrowne commented 1 year ago

For MSVC v17.5, the format() member function in a user defined std::formatter specialization does not have to be const, but for gcc v13.1 and clang trunk (for v17, as of today), the format() function must be const.

ognjenvukovicv commented 1 year ago

For latest MSVC in Compiler Explorer (GodBolt) => custom formatter is not working! I also tried it in the latest Visual Studio 2022 Preview Edition. Could anyone help?

davidbrowne commented 1 year ago

See https://godbolt.org/z/8Mc8nqfdx for an example with std::array. GodBolt doesn't execute MSVC code, but it does compile.

jonathanverner commented 10 months ago

It seems that, for clang's STL at least, the format function must be a template and can't just take std::format_context as argument (see https://github.com/llvm/llvm-project/issues/66466). I.e. one needs to write, for example,:

    ...
    template<class FormatContext>
      auto format(const MyCustomType& v, FormatContext& ctx) const {
        return std::format_to(ctx.out(), "my custom value");
      }
   ...

instead of just using std::format_context directly:

    ...
      auto format(const MyCustomType& v, std::format_context& ctx) const {
        return std::format_to(ctx.out(), "my custom value");
      }
   ...

See https://godbolt.org/z/67hcP66as which fails, as opposed to https://godbolt.org/z/5rrWobxP9, which succeeds.

mordante commented 3 months ago

It seems that, for clang's STL at least, the format function must be a template and can't just take std::format_context as argument (see llvm/llvm-project#66466). I.e. one needs to write, for example,:

I (the author of libc++'s format implementation) am confused about what the problem is. https://godbolt.org/z/67hcP66as fails with a linker error since there is no main function. https://godbolt.org/z/sbKzEYGdr renames testing to main and executes successfully.

If there is still an issue, could you file a bug-report for libc++? TIA.