This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of WP status.

3881. Incorrect formatting of container adapters backed by std::string

Section: 24.6.13 [container.adaptors.format] Status: WP Submitter: Victor Zverovich Opened: 2023-02-10 Last modified: 2023-02-13

Priority: Not Prioritized

View all issues with WP status.

Discussion:

According to 24.6.13 [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).

Previous resolution [SUPERSEDED]:

This wording is relative to N4928.

  1. Modify 24.6.13 [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);

[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.

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

Move to Immediate for C++23

[2023-02-13 Status changed: Immediate → WP.]

Proposed resolution:

This wording is relative to N4928.

  1. Modify 24.6.13 [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 26.2 [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);