3501. basic_syncbuf-related manipulators refer to some Allocator without defining it

Section: 31.7.6.5 [ostream.manip] Status: New Submitter: Jonathan Wakely Opened: 2020-11-15 Last modified: 2020-11-21

Priority: 3

View all other issues in [ostream.manip].

View all issues with New status.

Discussion:

From this editorial issue request:

The three basic_syncbuf-related manipulators emit_on_flush, noemit_on_flush, and flush_emit use in their Effects: elements the following wording:

"If os.rdbuf() is a basic_syncbuf<charT, traits, Allocator>*, called buf for the purpose of exposition, calls […]

There are two problems with that wording (even when considering the helpful note following p8): First, the type Allocator is not defined elsewhere (e.g. it is not part of the function signature) and second, os.rdbuf() has type basic_streambuf<charT, traits>* and not any other type.

By introducing an expository SYNCBUF to detect basic_syncbuf during the work on the above mentioned editorial issue to solve these problems it turned out that the suggested wording fix would introduce an apparently normative change that the syncbuf type must not use a program-defined specialization.

[2020-11-21; Reflector prioritization]

Set priority to 3 during reflector discussions.

Proposed resolution:

This wording is relative to N4868.

This proposed wording is known to be incorrect, but is nonetheless depicted to present the overall idea.

  1. Modify 31.7.6.5 [ostream.manip] as indicated:

    -1- Each instantiation of any of the function templates specified in this subclause is a designated addressable function (16.4.5.2.1 [namespace.std]).

    -?- In this subclause, SYNCBUF(p) for a pointer p of type B* is determined as follows. If *p is a base class subobject of an object of type S, where S is a specialization generated from the basic_syncbuf primary template, and is_convertible_v<S*, B*> is true, then SYNCBUF(p) is dynamic_cast<S*>(p). Otherwise, SYNCBUF(p) is static_cast<void*>(nullptr). [Note ?: To work around the issue that the Allocator template argument of S cannot be deduced, implementations can introduce an intermediate base class to basic_syncbuf that manages its emit_on_sync flag. — end note]

    […]

    template<class charT, class traits>
      basic_ostream<charT, traits>& emit_on_flush(basic_ostream<charT, traits>& os);
    

    Let p be SYNCBUF(os.rdbuf()).

    -8- Effects: If pos.rdbuf() is not nulla basic_syncbuf<charT, traits, Allocator>*, called buf for the purpose of exposition, calls pbuf->set_emit_on_sync(true). Otherwise this manipulator has no effect. [Note 1: To work around the issue that the Allocator template argument cannot be deduced, implementations can introduce an intermediate base class to basic_syncbuf that manages its emit_on_sync flag. — end note]

    -9- Returns: os.

    template<class charT, class traits>
      basic_ostream<charT, traits>& noemit_on_flush(basic_ostream<charT, traits>& os);
    

    Let p be SYNCBUF(os.rdbuf()).

    -10- Effects: If pos.rdbuf() is not nulla basic_syncbuf<charT, traits, Allocator>*, called buf for the purpose of exposition, calls pbuf->set_emit_on_sync(false). Otherwise this manipulator has no effect.

    -11- Returns: os.

    template<class charT, class traits>
      basic_ostream<charT, traits>& flush_emit(basic_ostream<charT, traits>& os);
    

    Let p be SYNCBUF(os.rdbuf()).

    -12- Effects: Calls os.flush(). Then, if pos.rdbuf() is not nulla basic_syncbuf<charT, traits, Allocator>*, called buf for the purpose of exposition, calls pbuf->emit().

    -13- Returns: os.