[libcxx-commits] [PATCH] D96664: [libc++][format] Implement formatters.

Arthur O'Dwyer via Phabricator via libcxx-commits libcxx-commits at lists.llvm.org
Sun Jun 27 14:15:00 PDT 2021


Quuxplusone added inline comments.


================
Comment at: libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.bool.pass.cpp:52-55
+  static_assert(std::is_copy_constructible_v<F>);
+  static_assert(std::is_move_constructible_v<F>);
+
+  static_assert(std::is_swappable_v<F>);
----------------
This was a vestige of the time when the C++20 concepts weren't in trunk yet, right?
I believe every instance of this pattern should be replaced with a one-liner:
```
    static_assert(std::semiregular<std::formatter<bool, CharT>>);
    test(...
```
In fact, consider moving the assertion up into `test`, where you already have a variable of the correct type, so you could just say
```
    static_assert(std::semiregular<decltype(formatter)>);
```
and wouldn't have to worry about typos potentially introduced by faulty cut-and-paste.


================
Comment at: libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.bool.pass.cpp:29-43
+template <class CharT, class A>
+void test(std::basic_string<CharT> expected, std::basic_string<CharT> fmt,
+          A arg) {
+  std::basic_format_parse_context<CharT> parse_ctx{fmt};
+  std::formatter<bool, CharT> formatter;
+  formatter.parse(parse_ctx);
+
----------------
Mordante wrote:
> Quuxplusone wrote:
> > I'd find this easier to read:
> > ```
> > template <class StringT, class ArithmeticT>
> > void test(StringT expected, StringT fmt, ArithmeticT arg) {
> >   using CharT = typename StringT::value_type;
> >   auto parse_ctx = std::basic_format_parse_context<CharT>(fmt);
> >   std::formatter<bool, CharT> formatter;
> >   formatter.parse(parse_ctx);
> > 
> >   StringT result;
> >   auto out = std::back_inserter(result);
> >   using FormatCtxT = std::basic_format_context<decltype(out), CharT>;
> > 
> >   auto format_ctx = FormatCtxT(out, std::make_format_args<FormatCtxT>(arg));
> >   formatter.format(arg, format_ctx);
> >   assert(result == expected);
> > }
> > ```
> > And then, if `ArithmeticT` is always `bool`, I strongly recommend making the substitution. Otherwise, you're implicitly relying on template type deduction down in `main` to deduce `bool` from `true` and `false` — which is obviously not-too-bad in this case, but you're going to have to use a different strategy once you reach the formatter for `short`. So I recommend switching strategies right now, in order to be consistent across all the new test files.
> > ```
> > template <class StringT>
> > void test(StringT expected, StringT fmt, bool arg) {
> > ```
> > (and then the rest as above)
> I used most of this suggestion and updated the other tests as well. In this case I prefer to not add another layer of indirection, instead I replaced the template argument `ArithmeticT` with a `bool`. 
LGTM! Btw, @vitaut, is this really the most ergonomic way to fit all these puzzle pieces together? I know this isn't the "expected way to use `<format>`," because the expected way is just `std::format("{}", arg)`... but this code feels very loop-de-loopy even for internal implementation stuff.

    std::formatter<bool, CharT> formatter;
        // ok cool so this knows how to format bools?
    auto format_ctx = FormatCtxT(out, std::make_format_args<FormatCtxT>(arg));
        // we're passing in `arg` here, so now it's formatted?
    formatter.format(arg, format_ctx);
        // no, ok, pass in `arg` AGAIN, third time's the charm!



================
Comment at: libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.c_string.pass.cpp:47-55
+template <class T>
+concept pointer = std::is_pointer_v<T>;
+
+template <class T>
+concept object = !pointer<T>;
+
+template <pointer CharPtr>
----------------
`pointer` is rightly named but unnecessary; you could just `requires is_pointer_v<CharPtr>` (or in fact do nothing at all, because this function doesn't need to be constrained for any technical reason). `object` is misnamed, since it is significantly different from `std::is_object_v` (but also, it's unused). Write instead:
```
template<class CStringT>
void test() {
    using CharT = std::remove_cv_t<std::remove_pointer_t<CStringT>>;
        // alternatively, = std::iter_value_t<CStringT>;
    static_assert(std::semiregular<std::formatter<CStringT, CharT>>);
    test<CStringT>(STR(" azAZ09,./<>?"), STR("}"), CSTR(" azAZ09,./<>?"));

    std::basic_string<CharT> s(CSTR("abc\0abc"), 7);
    test<CStringT>(STR("abc"), STR("}"), s.c_str());
}
```
I think what I've got for the embedded-NUL test ought to work. I have not tried it.


================
Comment at: libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.c_string.pass.cpp:80-88
+template <object CharT>
+void test() {
+  test<CharT*>();
+  test<const CharT*>();
+}
+
+int main(int, char**) {
----------------
I strongly recommend flattening these:
```
int main(int, char**) {
  test<char*>();
  test<const char*>();
  test<wchar_t*>();
  test<const wchar_t*>();
}
```
Even if you don't flatten them, at least use different names for `test` (the actual test) and `test` (the thing that calls `test` twice). I don't know our convention off the top of my head, but I know libcxx/test/ has plenty of examples of this to set naming precedent.


================
Comment at: libcxx/test/std/utilities/format/format.formatter/format.formatter.spec/formatter.const_char_array.pass.cpp:55-60
+    std::basic_string<CharT> buffer{text, text + N};
+    // Note not too found of this hack
+    Str* data = reinterpret_cast<Str*>(buffer.c_str());
+
+    Ctx ctx{std::back_inserter(result), std::make_format_args<Ctx>(*data)};
+    formatter.format(*data, ctx);
----------------
I think what you want here is
```
std::formatter<ArrayT, CharT> formatter;
  [...]
CharT array[N];
std::copy(text, text + N, array);
ArrayT& arg = array;
auto format_ctx = FormatCtxT(std::back_inserter(result), std::make_format_args<FormatCtxT>(arg));
formatter.format(arg, ctx);
```
Note consistent use of the names `FormatCtxT`, `arg`, etc. among all the test cases.
I admit I don't understand why you're doing the weird thing with the class-type NTTP `Tester` here; couldn't you just hard-code `N` in the test case? We don't need to "deduce the size of the input" when there //is// only one input. And then you can get rid of a lot of "magic" — no more CTAD/CNTTPs, no more `__builtin_memcpy`, etc.


================
Comment at: libcxx/test/std/utilities/format/format.functions/tests.inc:27-32
+  // ** Test invalid format strings ***
+  test_exception("The format string terminates while parsing a replacement "
+                 "field or an escape sequence",
+                 STR("{"));
+  test_exception("The replacement field misses the terminating '}'", STR("{:"),
+                 42);
----------------
IIUC, these tests are testing the wording of libc++-specific `what`-strings that are not mandated by the Standard, is that right? If so, they belong in libcxx/test/libcxx/.
Or perhaps it would be better to leave the tests here, but put every instance of `assert(e.what() == what)` under a libc++-specific `#ifdef`. That way, on non-libc++ libraries, we'd test that //some// exception was correctly thrown; we just wouldn't test its what-string.


================
Comment at: libcxx/test/std/utilities/format/format.functions/vformat_to.pass.cpp:36-41
+template <class CharT>
+bool operator==(const std::list<CharT>& lhs,
+                const std::basic_string<CharT>& rhs) {
+  return lhs.size() == rhs.size() &&
+         std::equal(lhs.begin(), lhs.end(), rhs.begin());
+}
----------------
Please don't provide `==`s for library types, not even in .cpp files. In this case your initial `size` check can be omitted anyway, because `std::equal` can do it: just change e.g. line 68 to
```
assert(std::equal(out.begin(), out.end(), expected.begin(), expected.end()));
```


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D96664/new/

https://reviews.llvm.org/D96664



More information about the libcxx-commits mailing list