Title
Incorrect formatting of container adapters backed by std::string
Status
c++23
Section
[container.adaptors.format]
Submitter
Victor Zverovich

Created on 2023-02-10.00:00:00 last changed 13 months ago

Messages

Date: 2023-02-13.11:31:32

Proposed resolution:

This wording is relative to n4928.

  1. Modify [container.adaptors.format] as indicated:

    -1- For each of queue, priority_queue, and stack, the library provides the following formatter specialization where adaptor-type is the name of the template:

    namespace std {
      template<class charT, class T, formattable<charT> Container, class... U>
      struct formatter<adaptor-type<T, Container, U...>, charT> {
      private:
        using maybe-const-container =                     // exposition only
          fmt-maybe-const<Container, charT>;
        using maybe-const-adaptor =                       // exposition only
          fmt-maybe-const<is_const_v<maybe-const-container>, adaptor-type<T, Container, U...>, charT>; // see [ranges.syn]
          
        formatter<ranges::ref_view<maybe-const-container>Container, charT> underlying_;          // exposition only
    
      public:
        template<class ParseContext>
          constexpr typename ParseContext::iterator
            parse(ParseContext& ctx);
    
        template<class FormatContext>
          typename FormatContext::iterator
            format(maybe-const-adaptor& r, FormatContext& ctx) const;
      };
    }
    
    […]
    template<class FormatContext>
      typename FormatContext::iterator
        format(maybe-const-adaptor& r, FormatContext& ctx) const;
    

    -3- Effects: Equivalent to: return underlying_.format(r.c, ctx);

Date: 2023-02-13.00:00:00

[ 2023-02-13 Approved at February 2023 meeting in Issaquah. Status changed: Immediate → WP. ]

Date: 2023-02-10.21:41:20

[ Issaquah 2023-02-10; LWG issue processing ]

Move to Immediate for C++23

Date: 2023-02-10.00:00:00

[ 2023-02-10 Tim provides updated wording ]

The container elements may not be const-formattable so we cannot use the const formatter unconditionally. Also the current wording is broken because an adaptor is not range and we cannot use fmt-maybe-const on the adaptor — only the underlying container.

Date: 2023-02-10.18:49:11

According to [container.adaptors.format] container adapters such as std::stack are formatted by forwarding to the underlying container:

template<class FormatContext>
  typename FormatContext::iterator
    format(maybe-const-adaptor& r, FormatContext& ctx) const;

Effects: Equivalent to: return underlying_.format(r.c, ctx);

This gives expected results for std::stack<T> and most types of underlying container:

auto s = std::format("{}", std::stack(std::deque{'a', 'b', 'c'}));
// s == "['a', 'b', 'c']"

However, when the underlying container is std::string the output is:

auto s = std::format("{}", std::stack{std::string{"abc"}});
// s == "abc"

This is clearly incorrect because std::stack itself is not a string (it is only backed by a string) and inconsistent with formatting of ranges where non-string range types are formatted as comma-separated values delimited by '[' and ']'. The correct output in this case would be ['a', 'b', 'c'].

Here is an illustration of this issue on godbolt using {fmt} and an implementation of the formatter for container adapters based on the one from the standard: https://godbolt.org/z/P1nrM1986.

A simple fix is to wrap the underlying container in std::views::all(_t) (https://godbolt.org/z/8MT1be838).

This wording is relative to n4928.

  1. Modify [container.adaptors.format] as indicated:

    -1- For each of queue, priority_queue, and stack, the library provides the following formatter specialization where adaptor-type is the name of the template:

    namespace std {
      template<class charT, class T, formattable<charT> Container, class... U>
      struct formatter<adaptor-type<T, Container, U...>, charT> {
      private:
        using maybe-const-adaptor =                       // exposition only
          fmt-maybe-const<adaptor-type<T, Container, U...>, charT>;
        formatter<views::all_t<const Container&>, charT> underlying_;          // exposition only
    
      public:
        template<class ParseContext>
          constexpr typename ParseContext::iterator
            parse(ParseContext& ctx);
    
        template<class FormatContext>
          typename FormatContext::iterator
            format(maybe-const-adaptor& r, FormatContext& ctx) const;
      };
    }
    
    […]
    template<class FormatContext>
      typename FormatContext::iterator
        format(maybe-const-adaptor& r, FormatContext& ctx) const;
    

    -3- Effects: Equivalent to: return underlying_.format(views::all(r.c), ctx);

History
Date User Action Args
2023-11-22 15:47:43adminsetstatus: wp -> c++23
2023-02-13 11:31:32adminsetmessages: + msg13406
2023-02-13 11:31:32adminsetstatus: immediate -> wp
2023-02-10 21:41:20adminsetmessages: + msg13345
2023-02-10 21:41:20adminsetstatus: new -> immediate
2023-02-10 18:49:11adminsetmessages: + msg13342
2023-02-10 17:06:30adminsetmessages: + msg13336
2023-02-10 00:00:00admincreate