JTC1/SC22/WG21
N1569
WG21 N1569=04-0009
PROPOSED FIXES TO LIBRARY INCONSISTENCIES
P.J. Plauger
Dinkumware, Ltd.
pjp@dinkumware.com
1) set::find is inconsistent with associative container requirements.
map/multimap have:
iterator find(const key_type& x) const;
const_iterator find(const key_type& x) const;
which is consistent with the table of associative container requirements.
But set/multiset have:
iterator find(const key_type&) const;
set/multiset should look like map/multimap, and honor the requirements
table, in this regard.
-------------
2) Associative erase should return an iterator.
map/multimap/set/multiset have:
void erase(iterator);
void erase(iterator, iterator);
But there's no good reason why these can't return an iterator, as for
vector/deque/list:
iterator erase(iterator);
iterator erase(iterator, iterator);
The table of associative container requirements, and the relevant
template classes, should return an iterator designating the first
element beyond the erased subrange.
-------------
3) locale::combine should be permitted to generate a named locale.
template<class Facet>
locale::combine(const locale&) const;
is obliged to create a locale that has no name. This is overspecification
and overkill. The resulting locale should follow the usual rules -- it
has a name if the locale argument has a name and Facet is one of the
standard facets.
-------------
4) basic_stringbuf::seekoff need not always fail for an empty stream.
pos_type basic_stringbuf::seekoff(off_type, ios_base::seekdir,
ios_base::openmode);
is obliged to fail if nothing has been inserted into the stream. This
is unnecessary and undesirable. It should be permissible to seek to
an effective offset of zero.
-------------
5) basic_filebuf::open should accept wchar_t names.
basic_filebuf *basic_filebuf::open(const char *, ios_base::open_mode);
should be supplemented with the overload:
basic_filebuf *basic_filebuf::open(const wchar_t *, ios_base::open_mode);
Depending on the operating system, one of these forms is fundamental and
the other requires an implementation-defined mapping to determine the
actual filename.
-------------
6) cerr::tie() and wcerr::tie() are overspecified.
Both cerr::tie() and wcerr::tie() are obliged to be null at program
startup. This is overspecification and overkill. It is both traditional
and useful to tie cerr to cout, to ensure that standard output is drained
whenever an error message is written. This behavior should at least be
permitted if not required. Same for wcerr::tie().
-------------
7) Traditional C header files are overspecified.
The C++ Standard effectively requires that the traditional C headers (of
the form <xxx.h>) be defined in terms of the newer C++ headers (of the form
<cxxx>). Clauses 17.4.1.2/4 and D.5 combine to require that:
-- Including the header <cxxx> declares a C name in namespace std.
-- Including the header <xxx.h> declares a C name in namespace std
(effectively by including <cxxx>), then imports it into the global
namespace with an individual using declaration.
The rules were left in this form despited repeated and heated objections
from several compiler vendors. The C headers are often beyond the direct
control of C++ implementors. In some organizations, it's all they can do
to get a few #ifdef __cplusplus tests added. Third-party library vendors
can perhaps wrap the C headers. But neither of these approaches supports
the drastic restructuring required by the C++ Standard. As a result, it is
still widespread practice to ignore this conformance requirement, nearly
seven years after the committee last debated this topic. Instead, what is
often implemented is:
-- Including the header <xxx.h> declares a C name in the global namespace.
-- Including the header <cxxx> declares a C name in the global namespace
(effectively by including <xxx.h>), then imports it into namespace std
with an individual using declaration.
The practical benefit for implementors with the second approach is that
they can use existing C library headers, as they are pretty much obliged
to do. The practical cost for programmers facing a mix of implementations
is that they have to assume weaker rules:
-- If you want to assuredly declare a C name in the global namespace,
include <xxx.h>. You may or may not also get the declaration in namespace
std.
-- If you want to assuredly declare a C name in namespace std, include
<cxxx.h>. You may or may not also get the declaration in the global
namespace.
There also exists the *possibility* of subtle differences due to Koenig
lookup, but there are so few non-builtin types defined in the C headers
that I've yet to see an example of any real problems in this area.
It is worth observing that the rate at which programmers fall afoul of
these differences has remained small, at least as measured by newsgroup
postings and our own bug reports. (By an overwhelming margin, the
commonest problem is still that programmers include <string> and can't
understand why the typename string isn't defined -- this a decade after
the committee invented namespace std, nominally for the benefit of all
programmers.)
We should accept the fact that we made a serious mistake and rectify it,
however belatedly, by explicitly allowing either of the two schemes for
declaring C names in headers.