Document #: | P2867R1 |
Date: | 2023-09-15 |
Project: | Programming Language C++ |
Audience: |
Library Evolution Incubator |
Reply-to: |
Alisdair Meredith <ameredith1@bloomberg.net> |
Annex D of the C++ Standard, deprecated features, maintains a limited
and easily misused iostreams facility for
char *
strings in user-managed
memory. This paper proposes the removing this facility from the C++
Standard Library as C++20 and C++23 have provided superior replacement
facilities.
<strstream>
from the table of all standard headersOriginal version of this document, extracted from the C++23 proposal [P2139R2].
Key changes since that earlier paper:
At the start of the C++23 cycle, [P2139R2] tried to review each deprecated feature of C++, to see which we would benefit from actively removing, and which might now be better undeprecated. Consolidating all this analysis into one place was intended to ease the (L)EWG review process, but in return gave the author so much feedback that the next revision of that paper was not completed.
For the C++26 cycle there will be a concise paper tracking the overall review process, [P2863R1], but all changes to the standard will be pursued through specific papers, decoupling progress from the larger paper so that delays on a single feature do not hold up progress on all.
This paper takes up the deprecated
strstream
facility from the
original C++98 standard, D.14
[depr.str.strstreams].
The char*
streams were
provided, pre-deprecated, in C++98 and have been considered for removal
before. The underlying principle of previous reviews is that this
facility not be removed until suitable replacement functionality is
available in the standard for users to migrate to.
C++20 landed the ability to move strings efficiently out of
stringstreams in [P0408R7]. C++23
landed the spanstream
library
[P0448R4], which is a candidate for the
replacement functionality, so for C++26 we can seriously consider
removing this legacy feature, the largest and oldest deprecated feature
in the standard. The Zombie Names clause provides library vendors the
ability to retain support for these libraries long after the standard
has stopped specifying them. Therefore the preferred recommendation of
this paper is to finally remove this library from the C++26
Standard.
There remains the alternative position that this facility has been a supported shipping part of the C++ Standard for almost 30 years when C++26 ships. If we have not made serious moves to remove the library in all that time, maybe we should consider undeprecating, and taking away the shadow of doubt over any code that reaches for this facility today.
We note that there are two open issues that should be resolved as part of any attempt to undeprecate this facility:
strstreambuf
refers to
nonexistent member of fpos
,
fpos::offset
strstreambuf
is copyableBoth issues could be closed without further work if the
char *
streams are removed.
Remove the long deprecated
char *
streams from C++26.
Make the following changes to the C++ Working Draft. All wording is relative to [N4958], the latest draft at the time of writing.
<strstream>
from the list
of standard headers2 The C++ standard library provides the C++ library headers, shown in Table 24.
Table 24: C++ library headers [tab:headers.cpp]
<algorithm> |
<format> |
<new> |
<stdexcept> |
<any> |
<forward_list> |
<numbers> |
<stdfloat> |
<array> |
<fstream> |
<numeric> |
<stop_token> |
<atomic> |
<functional> |
<optional> |
<streambuf> |
<barrier> |
<future> |
<ostream> |
<string> |
<bit> |
<generator> |
<print> |
<string_view> |
<bitset> |
<initializer_list> |
<queue> |
<strstream> |
<charconv> |
<iomanip> |
<random> |
<syncstream> |
<chrono> |
<ios> |
<ranges> |
<system_error> |
<codecvt> |
<iosfwd> |
<ratio> |
<text_encoding> |
<compare> |
<iostream> |
<rcu> |
<thread> |
<complex> |
<istream> |
<regex> |
<tuple> |
<concepts> |
<iterator> |
<scoped_allocator> |
<type_traits> |
<condition_variable> |
<latch> |
<semaphore> |
<typeindex> |
<coroutine> |
<limits> |
<set> |
<typeinfo> |
<deque> |
<list> |
<shared_mutex> |
<unordered_map> |
<exception> |
<locale> |
<source_location> |
<unordered_set> |
<execution> |
<map> |
<span> |
<utility> |
<expected> |
<mdspan> |
<spanstream> |
<valarray> |
<filesystem> |
<memory> |
<sstream> |
<variant> |
<flat_map> |
<memory_resource> |
<stack> |
<vector> |
<flat_set> |
<mutex> |
<stacktrace> |
<version> |
1
In namespace std
, the following
names are reserved for previous standardization:
auto_ptr
,auto_ptr_ref
,binary_function
,binary_negate
,bind1st
,bind2nd
,binder1st
,binder2nd
,const_mem_fun1_ref_t
,const_mem_fun1_t
,const_mem_fun_ref_t
,const_mem_fun_t
,declare_no_pointers
,declare_reachable
,get_pointer_safety
,get_temporary_buffer
,get_unexpected
,gets
,is_literal_type
,is_literal_type_v
,istrstream
,mem_fun1_ref_t
,mem_fun1_t
,mem_fun_ref_t
,mem_fun_ref
,mem_fun_t
,mem_fun
,not1
,not2
,ostrstream
,pointer_safety
pointer_to_binary_function
,pointer_to_unary_function
,ptr_fun
,random_shuffle
,raw_storage_iterator
,result_of
,result_of_t
,return_temporary_buffer
,set_unexpected
,strstream
,strstreambuf
,unary_function
,unary_negate
,uncaught_exception
,undeclare_no_pointers
,undeclare_reachable
,
andunexpected_handler
.2 The following names are reserved as members for previous standardization, and may not be used as a name for object-like macros in portable code:
argument_type
,first_argument_type
,io_state
,open_mode
,preferred
,second_argument_type
,seek_dir
, andstrict
.3
The name
following names are reserved as member
functions for previous standardization, and may not be used
as a name for function-like macros in portable code.stossc
is reserved
as a member function
freeze
,pcount
, andstossc
4
The header names
<ccomplex>
,
<ciso646>
,
<cstdalign>
,
<cstdbool>
, and
<ctgmath>
, and
<strstream
>
are reserved for previous standardization.
Change: Remove header
<strstream>
and all its
contents.
Rationale: The header has been deprecated since the
original C++ Standard, as its facilities required more care to user
correctly than preferred to parts of the C++ Standard Library; the
<spanstream>
header
provides an updated safer facility. Ongoing support remains at
implementer’s discretion, exercising freedoms granted by
16.4.5.3.2
[zombie.names].
Effect on original feature: A valid C++ 2023 program
#include
-ing the header may fail
to compile. Code that uses any of the following classes by importing one
of the standard library modules may fail to compile:
istrstream
ostrstream
strstream
strstreambuf
char *
streamschar*
streams<strstream>
synopsis1
The header <strstream>
defines types that associate stream buffers with character array objects
and assist reading and writing such objects.
namespace std {
class strstreambuf;
class istrstream;
class ostrstream;
class strstream; }
strstreambuf
namespace std {
class strstreambuf : public basic_streambuf<char> {
public:
strstreambuf() : strstreambuf(0) {}
explicit strstreambuf(streamsize alsize_arg);
strstreambuf(void* (*palloc_arg)(size_t), void (*pfree_arg)(void*));
strstreambuf(char* gnext_arg, streamsize n, char* pbeg_arg = nullptr);
strstreambuf(const char* gnext_arg, streamsize n);
strstreambuf(signed char* gnext_arg, streamsize n,
signed char* pbeg_arg = nullptr);
strstreambuf(const signed char* gnext_arg, streamsize n);
strstreambuf(unsigned char* gnext_arg, streamsize n,
unsigned char* pbeg_arg = nullptr);
strstreambuf(const unsigned char* gnext_arg, streamsize n);
virtual ~strstreambuf();
void freeze(bool freezefl = true);
char* str();
int pcount();
protected:
int_type overflow (int_type c = EOF) override;
int_type pbackfail(int_type c = EOF) override;
int_type underflow() override;
pos_type seekoff(off_type off, ios_base::seekdir way,
ios_base::openmode which = ios_base::in | ios_base::out) override;
pos_type seekpos(pos_type sp,
ios_base::openmode which = ios_base::in | ios_base::out) override;
streambuf* setbuf(char* s, streamsize n) override;
private:
using strstate = T1; // exposition only
static const strstate allocated; // exposition only
static const strstate constant; // exposition only
static const strstate dynamic; // exposition only
static const strstate frozen; // exposition only
strstate strmode; // exposition only
streamsize alsize; // exposition only
void* (*palloc)(size_t); // exposition only
void (*pfree)(void*); // exposition only
}; }
1
The class strstreambuf
associates the input sequence, and possibly the output sequence, with an
object of some character array type, whose elements store
arbitrary values. The array object has several attributes.
2 [Note 1: For the sake of exposition, these are represented as elements of a bitmask type (indicated here as T1) called strstate. The elements are:]
3 [Note 2: For the sake of exposition, the maintained data is presented here as:]
4
Each object of class
strstreambuf
has a seekable
area, delimited by the pointers
seeklow
and
seekhigh
. If
gnext
is a null pointer, the
seekable area is undefined. Otherwise,
seeklow
equals
gbeg
and
seekhigh
is either
pend
, if
pend
is not a null pointer, or
gend
.
strstreambuf
constructors explicit strstreambuf(streamsize alsize_arg);
1
Effects: Initializes the base class with
streambuf()
. The postconditions
of this function are indicated in Table 147.
Table 147:
strstreambuf(streamsize)
effects
[tab:depr.strstreambuf.cons.sz]
Element
|
Value
|
---|---|
strmode |
dynamic |
alsize |
alsize_arg |
palloc |
a null pointer |
pfree |
a null pointer |
2
Effects: Initializes the base class with
streambuf()
. The postconditions
of this function are indicated in Table 148.
Table 148: strstreambuf(void* (*)(size_t), void (*)(void*))
effects
Element
|
Value
|
---|---|
strmode |
dynamic |
alsize |
an unspecified value |
palloc |
palloc_arg |
pfree |
pfree_arg |
strstreambuf(char* gnext_arg, streamsize n, char* pbeg_arg = nullptr);
strstreambuf(signed char* gnext_arg, streamsize n,
signed char* pbeg_arg = nullptr);
strstreambuf(unsigned char* gnext_arg, streamsize n, unsigned char* pbeg_arg = nullptr);
3
Effects: Initializes the base class with
streambuf()
. The postconditions
of this function are indicated in Table 149.
Table 149: strstreambuf(charT*, streamsize, charT*)
effects [tab:depr.strstreambuf.cons.ptr]
Element
|
Value
|
---|---|
strmode |
0 |
alsize |
an unspecified value |
palloc |
a null pointer |
pfree |
a null pointer |
4
gnext_arg
shall point to the
first element of an array object whose number of elements
N
is determined as follows:
n > 0
,
N
is
n
.n == 0
,
N
is
std::strlen(gnext_arg)
.n < 0
,
N
is
INT_MAX
.15
If pbeg_arg
is a null pointer,
the function executes:
setg(gnext_arg, gnext_arg, gnext_arg + N);
6 Otherwise, the function executes:
setg(gnext_arg, gnext_arg, pbeg_arg); setp(pbeg_arg, pbeg_arg + N);
strstreambuf(const char* gnext_arg, streamsize n);
strstreambuf(const signed char* gnext_arg, streamsize n); strstreambuf(const unsigned char* gnext_arg, streamsize n);
7
Effects: Behaves the same as
strstreambuf((char*)gnext_arg,n)
,
except that the constructor also sets constant in
strmode
.
virtual ~strstreambuf();
8
Effects: Destroys an object of class
strstreambuf
. The function frees
the dynamically allocated array object only if
(strmode & allocated) != 0
and (strmode & frozen) == 0
.
(D.14.2.4
[depr.strstreambuf.virtuals]
describes how a dynamically allocated array object is freed.)
void freeze(bool freezefl = true);
1
Effects: If
strmode & dynamic
is
nonzero, alters the freeze status of the dynamic array object as
follows:
freezefl
is
true
, the function sets
frozen
in
strmode
.frozen
in strmode
.
char* str();
2
Effects: Calls
freeze()
, then returns the
beginning pointer for the input sequence,
gbeg
.
3 Remarks: The return value can be a null pointer.
int pcount() const;
4
Effects: If the next pointer for the output sequence,
pnext
, is a null pointer,
returns zero. Otherwise, returns the current effective length of the
array object as the next pointer minus the beginning pointer for the
output sequence,
pnext - pbeg
.
strstreambuf
overridden virtual
functions int_type overflow(int_type c = EOF) override;
1
Effects: Appends the character designated by
c
to the output sequence, if
possible, in one of two ways:
c != EOF
and if either
the output sequence has a write position available or the function makes
a write position available (as described below), assigns
c
to
*pnext++
.(unsigned char)c
.c == EOF
, there is no
character to append.EOF
.2
Returns EOF
to indicate
failure.
3 Remarks: The function can alter the number of write positions available as a result of any call.
4
To make a write position available, the function reallocates (or
initially allocates) an array object with a sufficient number of
elements n
to hold the current
array object (if any), plus at least one additional write position. How
many additional write positions are made available is otherwise
unspecified. If palloc
is not a
null pointer, the function calls
(*palloc)(n)
to allocate the new
dynamic array object. Otherwise, it evaluates the expression
new charT[n]
. In either case, if
the allocation fails, the function returns
EOF
. Otherwise, it sets
allocated
in
strmode
.
5
To free a previously existing dynamic array object whose first element
address is p
: If
pfree
is not a null pointer, the
function calls (*pfree)(p)
.
Otherwise, it evaluates the expression
delete[]p
.
6
If (strmode & dynamic) == 0
,
or if
(strmode & frozen) != 0
, the
function cannot extend the array (reallocate it with greater length) to
make a write position available.
7
Recommended practice: An implementation should consider
alsize
in making the decision
how many additional write positions to make available.
int_type pbackfail(int_type c = EOF) override;
8 Puts back the character designated by c to the input sequence, if possible, in one of three ways:
c != EOF
, if the input
sequence has a putback position available, and if
(char)c == gnext[-1]
, assigns
gnext - 1
to
gnext
.c
.strmode & constant
is
zero, assigns c
to
*--gnext
.c == EOF
and if the input
sequence has a putback position available, assigns
gnext - 1
to
gnext
.EOF
.9
Returns EOF
to indicate
failure.
10 Remarks: If the function can succeed in more than one of these ways, it is unspecified which way is chosen. The function can alter the number of putback positions available as a result of any call.
int_type underflow() override;
11 Effects: Reads a character from the input sequence, if possible, without moving the stream position past it, as follows:
(unsigned char)*gnext
.pnext
is not a null pointer and
is greater than the current read end pointer
gend
, makes a read
position available by assigning to
gend
a value greater than
gnext
and no greater than
pnext
.(unsigned char)*gnext.
12 Returns
EOF
to indicate failure.
13 Remarks: The function can alter the number of read positions available as a result of any call.
pos_type seekoff(off_type off, seekdir way, openmode which = in | out) override;
14 Effects: Alters the stream position within one of the controlled sequences, if possible, as indicated in Table 150.
Table 150: seekoff positioning [tab:depr.strstreambuf.seekoff.pos]
Conditions | Result |
(which & ios::in) != 0 |
positions the input sequence |
(which & ios::out) != 0 |
positions the output sequence |
(which & (ios::in | ios::out)) == (ios::in | ios::out)
and either way == ios::beg or
way == ios::end |
positions both the input and the output sequences |
Otherwise | the positioning operation fails. |
15 For a
sequence to be positioned, if its next pointer is a null pointer, the
positioning operation fails. Otherwise, the function determines
newoff
as indicated in Table
151.
Table 151: newoff values [tab:depr.strstreambuf.seekoff.newoff]
Condition | newoff Value |
way == ios::beg |
0 |
way == ios::cur |
the next pointer minus the beginning pointer
(xnext - xbeg ). |
way == ios::end |
seekhigh minus the beginning pointer
(seekhigh - xbeg ). |
16 If
(newoff + off) < (seeklow - xbeg)
or (seekhigh - xbeg) < (newoff + off)
,
the positioning operation fails. Otherwise, the function assigns
xbeg + newoff + off
to the next
pointer xnext
.
17
Returns:
pos_type(newoff)
, constructed
from the resultant offset newoff
(of type off_type
), that stores
the resultant stream position, if possible. If the positioning operation
fails, or if the constructed object cannot represent the resultant
stream position, the return value is
pos_type(off_type(-1))
.
pos_type seekpos(pos_type sp, ios_base::openmode which = ios_base::in | ios_base::out) override;
18
Effects: Alters the stream position within one of the
controlled sequences, if possible, to correspond to the stream position
stored in sp
(as described
below).
(which & ios::in) != 0
,
positions the input sequence.(which & ios::out) != 0
,
positions the output sequence.19 For a
sequence to be positioned, if its next pointer is a null pointer, the
positioning operation fails. Otherwise, the function determines
newoff
from
sp.offset()
:
newoff
is an invalid
stream position, has a negative value, or has a value greater than
(seekhigh - seeklow
), the
positioning operation failsnewoff
to the beginning pointer
xbeg
and store the result in the
next pointer xnext
.20
Returns:
pos_type(newoff)
, constructed
from the resultant offset newoff
(of type off_type
), that stores
the resultant stream position, if possible. If the positioning operation
fails, or if the constructed object cannot represent the resultant
stream position, the return value is
pos_type(off_type(-1))
.
streambuf<char>* setbuf(char* s, streamsize n) override;
21
Effects: Behavior is implementation-defined, except that
setbuf(0, 0)
has no effect.
istrstream
namespace std {
class istrstream : public basic_istream<char> {
public:
explicit istrstream(const char* s);
explicit istrstream(char* s);
istrstream(const char* s, streamsize n);
istrstream(char* s, streamsize n);
virtual ~istrstream();
strstreambuf* rdbuf() const;
char* str();
private:
strstreambuf sb; // exposition only
}; }
1
The class istrstream
supports
the reading of objects of class
strstreambuf
. It supplies a
strstreambuf
object to control
the associated array object. For the sake of exposition, the maintained
data is presented here as:
sb
, the
strstreambuf
object.istrstream
constructors
explicit istrstream(const char* s); explicit istrstream(char* s);
1
Effects: Initializes the base class with
istream(&sb)
and
sb
with
strstreambuf(s, 0)
.
s
shall designate the first
element of an NTBS.
istrstream(const char* s, streamsize n); istrstream(char* s, streamsize n);
2
Effects: Initializes the base class with
istream(&sb)
and
sb
with
strstreambuf(s,n)
.
s
shall designate the first
element of an array whose length is
n
elements, and
n
shall be greater than
zero.
strstreambuf* rdbuf() const;
1
Returns: const_cast<strstreambuf*>(&sb)
.
char* str();
2
Returns:
rdbuf()->str()
.
ostrstream
namespace std {
class ostrstream : public basic_ostream<char> {
public:
ostrstream();
ostrstream(char* s, int n, ios_base::openmode mode = ios_base::out);
virtual ~ostrstream();
strstreambuf* rdbuf() const;
void freeze(bool freezefl = true);
char* str();
int pcount() const;
private:
strstreambuf sb; // exposition only
}; }
1
The class ostrstream
supports
the writing of objects of class
strstreambuf
. It supplies a
strstreambuf
object to control
the associated array object. For the sake of exposition, the maintained
data is presented here as:
sb
, the
strstreambuf
object. ostrstream();
1
Effects: Initializes the base class with
ostream(&sb)
and
sb
with
strstreambuf()
.
ostrstream(char* s, int n, ios_base::openmode mode = ios_base::out);
2
Effects: Initializes the base class with
ostream(&sb)
, and
sb
with one of two
constructors:
(mode & app) == 0
,
then s
shall designate the first
element of an array of n
elements. The constructor is
strstreambuf(s, n, s)
.(mode & app) != 0
,
then s
shall designate the first
element of an array of n
elements that contains an NTBS whose first element is designated by
s
. The constructor is strstreambuf(s, n, s + std::strlen(s))
.2 strstreambuf* rdbuf() const;
1
Returns:
(strstreambuf*)&sb
.
void freeze(bool freezefl = true);
2
Effects: Calls
rdbuf()->freeze(freezefl)
.
char* str();
3
Returns:
rdbuf()->str()
.
int pcount() const;
4
Returns:
rdbuf()->pcount()
.
strstream
namespace std {
class strstream
: public basic_iostream<char> {
public:
// types
using char_type = char;
using int_type = char_traits<char>::int_type;
using pos_type = char_traits<char>::pos_type;
using off_type = char_traits<char>::off_type;
// constructors/destructor
strstream();
strstream(char* s, int n,
ios_base::openmode mode = ios_base::in|ios_base::out);
virtual ~strstream();
// members
strstreambuf* rdbuf() const;
void freeze(bool freezefl = true);
int pcount() const;
char* str();
private:
strstreambuf sb; // exposition only
}; }
1
The class strstream
supports
reading and writing from objects of class
strstreambuf
. It supplies a
strstreambuf
object to control
the associated array object. For the sake of exposition, the maintained
data is presented here as:
sb
, the
strstreambuf
object.strstream
constructors strstream();
1
Effects: Initializes the base class with
iostream(&sb)
.
strstream(char* s, int n, ios_base::openmode mode = ios_base::in|ios_base::out);
2
Effects: Initializes the base class with
iostream(&sb)
, and
sb
with one of the two
constructors:
(mode & app) == 0
,
then s shall designate the first element of an array of
n
elements. The constructor is
strstreambuf(s,n,s)
.(mode & app) != 0
,
then s shall designate the first element of an array of
n
elements that contains an NTBS
whose first element is designated by
s
. The constructor is strstreambuf(s,n,s+ std::strlen(s))
.strstream
destructor virtual ~strstream();
1
Effects: Destroys an object of class
strstream
.
strstream
operations strstreambuf* rdbuf() const;
1
Returns: const_cast<strstreambuf*>(&sb)
.
void freeze(bool freezefl = true);
2
Effects: Calls
rdbuf()->freeze(freezefl)
.
char* str();
3
Returns:
rdbuf()->str()
.
int pcount() const;
4
Returns:
rdbuf()->pcount()
.
All clause and subclause labels from ISO C++ 2023 (ISO/IEC 14882:2023, Programming Languages — C++) are present in this document, with the exceptions described below.
container.gen.reqmts see
container.requirements.general
depr.istrstream
removed
depr.istrstream.cons
removed
depr.istrstream.general
removed
depr.istrstream.members
removed
depr.ostrstream
removed
depr.ostrstream.cons
removed
depr.ostrstream.general
removed
depr.ostrstream.members
removed
depr.res.on.required removed
depr.str.strstreams
removed
depr.strstream
removed
depr.strstream.cons
removed
depr.strstream.dest
removed
depr.strstream.general
removed
depr.strstream.oper
removed
depr.strstream.syn
removed
depr.strstreambuf
removed
depr.strstreambuf.cons
removed
depr.strstreambuf.general
removed
depr.strstreambuf.members
removed
depr.strstreambuf.virtuals
removed
The following library issues should be resolved as NAD as they no longer apply to the C++ Standard due to the removal of the feature.
strstreambuf
refers to
nonexistent member of fpos
,
fpos::offset
strstreambuf
is copyableThanks to Michael Park for the pandoc-based framework used to transform this document’s source from Markdown.
Huge thanks to Peter Sommerlad for doing all the hard work to make removal of this tricky legacy facility possible.
The function signature
strlen(const char*)
is declared
in <cstring>
(23.5.3
[cstring.syn]). The
macro INT_MAX
is defined in
<climits>
(17.3.6
[climits.syn]).↩︎
The function signature
strlen(const char*)
is declared
in <cstring>
(23.5.3
[cstring.syn]).↩︎