This report is intended to serve as the basis for one or more defect reports to be processed by the committee's Library Working Group.
Pursuant to a commitment made at the Redmond (October, 2001) meeting, we undertook a review of clause 27 of the C++ Standard. The initial motivation for this review was Library Issue 73 (``is_open should be const''), classified by the committee as NAD with the sole rationale, ``This is a deliberate feature; const streams would be meaningless.'' We strongly disagreed with this disposition (especially since other, similar, issues were classified as DR's: 14, 110, 148, and 214) and sought to re-open the issue.
We were gently made aware by other committee members that clause 27 was somewhat notorious for its relative laxity with respect to issues of const-correctness. Further, we were less gently advised that the committee would prefer to avoid a piecemeal approach in addressing such issues.
Accordingly, we undertook to locate and propose remedies for all const-correctness problems throughout clause 27. In particular, we sought to identify those member functions in whose declarations a useful const was missing, or in which a const was present inappropriately. This paper presents the results of our review, one section for each of these two goals. Within these sections, we proceed sequentially through the sections of clause 27.
Section summary: none of the following member functions is currently declared const, but we believe that each should be.
The rationale for this set of recommendations is straightforward: each of these functions is responsible for reporting on the existing state of its respective class instance and none of these functions need alter the instance's logical state to do so.
Before presenting the annotated list of functions in this category, we wish to show a motivating example. This rather elementary example is intended to contradict the claim, evidently previously endorsed by the committee, that ``const streams would be meaningless''; it seems clear that the following code is meaningful indeed:
void f( const std::istream& is ) { if( is.in_avail() > 0 ) { /*...*/ } }
The failure of such code to compile presents a surprise to users.
To avoid duplication, we stress here that, to the best of our understanding and belief, each of the following functions is intended solely to report on some aspect of the state of its invoking object, and can do so without mutating that instance. In some cases, we believe, functions were not previously declared const because they seemed to need invoke other non-const functions. We have addressed all of these, too, either within this section or in the next section.
§27.5.2 and §27.5.2.2.3
Proposed declaration: streamsize in_avail() const;
§27.5.2 and §27.5.2.4.3
Proposed declaration: virtual streamsize showmanyc() const;
We strongly believe that this function should be declared const. However, the ramifications of this change require more study and so we are not now proposing this change. We expect to do so via a future paper.
We strongly believe that this function should be declared const. As is the case for tellg above, the ramifications of this change require more study and so we are not now proposing this change. We expect to do so via a future paper.
§27.8.1.1 and §27.8.1.4 above paragraph 1
Proposed declaration: virtual streamsize showmanyc() const;
§27.8.1.5 and §27.8.1.7 above paragraph 2
Proposed declaration: bool is_open() const;
§27.8.1.8 and §27.8.1.10 above paragraph 2
Proposed declaration: bool is_open() const;
§27.8.1.11 and §27.8.1.13 above paragraph 2
Proposed declaration: bool is_open() const;
Section summary: the following member functions are declared const, yet return non-const pointers. We believe, with one exception, that all of these are troublesome in that they allow code which is likely to surprise the user.
We believe the guarantee provided by a const member function is most valuable to users when it refers to ``logical const-ness'', rather than to ``bitwise const-ness'': one should not be able to change the observable state of on object to which one has only const-qualified access. To this end, we believe that a const member function should only rarely, if ever, return a non-const pointer (or reference) to a data member, because this allows a user to change the state of the invoking object even when given only const access to that object. A single example will suffice to demonstrate this:
Under the current wording, the function basic_ios<charT,traits>::rdbuf is declared const, but returns a non-const pointer. This means that the following code is well-formed:
void f(const std::basic_ios<char>& str) { str.rdbuf()->sputc('x'); }
This function f will alter the stream str, even though str, as f's argument, is declared const. This behavior is very likely to surprise a user of f.
We propose that all of the problems of this kind can be solved via the application of a standard idiom: replacing each such misguided member function by a pair of member functions such that
Under our proposal, such a function f would not compile. Such a change could break currently working code. However, the purpose of this proposal is to make such code illegal: if the implementer of f wishes to modify the object it is passed, then the argument str should not be declared const.
We stress, to eliminate considerable duplicate verbiage, that each of the following functions is declared const at present, yet returns a non-const pointer.
§27.4.4
We draw attention to the fact that, although operator void* is declared const, and while the return value is not declared to be const, we believe that no change is necessary. The returned pointer is not the address of a data member; so the lack of const-ness in the return type seems innocuous.
§27.4.4 and §27.4.4.2
We propose to replace each of tie and rdbuf by a pair of functions:
basic_ostream<charT,traits>* tie(); const basic_ostream<charT,traits>* tie() const; basic_streambuf<charT,traits>* rdbuf(); const basic_streambuf<charT,traits>* rdbuf() const;
§27.5.2 and §27.5.2.3.1
We propose to replace each of eback, gptr, and rdbuf by a pair of functions:
char_type* eback(); const char_type* eback() const; char_type* gptr(); const char_type* gptr() const; char_type* egptr(); const char_type* egptr() const;
§27.5.2 and §27.5.2.3.2
We propose to replace each of pbase, pptr, and epptr by a pair of functions:
char_type* pbase(); const char_type* pbase() const; char_type* pptr(); const char_type* pptr() const; char_type* epptr(); const char_type* epptr() const;
§27.7.2 and §27.7.2.2
We propose to replace rdbuf by a pair of functions:
basic_stringbuf<charT,traits,Allocator>* rdbuf(); const basic_stringbuf<charT,traits,Allocator>* rdbuf() const;
§27.7.3 and §27.7.3.2
We propose to replace rdbuf by a pair of functions:
basic_stringbuf<charT,traits,Allocator>* rdbuf(); const basic_stringbuf<charT,traits,Allocator>* rdbuf() const;
§27.7.4 and §27.7.6
We propose to replace rdbuf by a pair of functions:
basic_stringbuf<charT,traits,Allocator>* rdbuf(); const basic_stringbuf<charT,traits,Allocator>* rdbuf() const;
§27.8.1.5 and §27.8.1.7 above paragraph 1
We propose to replace rdbuf by a pair of functions:
basic_filebuf<charT,traits>* rdbuf(); const basic_filebuf<charT,traits>* rdbuf() const;
§27.8.1.8 and §27.8.1.10 above paragraph 1
We propose to replace rdbuf by a pair of functions:
basic_filebuf<charT,traits>* rdbuf(); const basic_filebuf<charT,traits>* rdbuf() const;
§27.8.1.11 and §27.8.1.13 above paragraph 1
We propose to replace rdbuf by a pair of functions:
basic_filebuf<charT,traits>* rdbuf(); const basic_filebuf<charT,traits>* rdbuf() const;