| Doc. no. | N2000=06-0070 |
| Date: | 2006-04-21 |
| Project: | Programming Language C++ |
| Reply to: | Howard Hinnant <howard.hinnant@gmail.com> |
Reference ISO/IEC IS 14882:1998(E)
Also see:
The purpose of this document is to record the status of issues which have come before the Library Working Group (LWG) of the ANSI (J16) and ISO (WG21) C++ Standards Committee. Issues represent potential defects in the ISO/IEC IS 14882:1998(E) document. Issues are not to be used to request new features.
This document contains only library issues which are actively being considered by the Library Working Group. That is, issues which have a status of New, Open, Ready, and Review. See Library Defect Reports List for issues considered defects and Library Closed Issues List for issues considered closed.
The issues in these lists are not necessarily formal ISO Defect Reports (DR's). While some issues will eventually be elevated to official Defect Report status, other issues will be disposed of in other ways. See Issue Status.
This document is in an experimental format designed for both viewing via a world-wide web browser and hard-copy printing. It is available as an HTML file for browsing or PDF file for printing.
Prior to Revision 14, library issues lists existed in two slightly different versions; a Committee Version and a Public Version. Beginning with Revision 14 the two versions were combined into a single version.
This document includes [bracketed italicized notes] as a reminder to the LWG of current progress on issues. Such notes are strictly unofficial and should be read with caution as they may be incomplete or incorrect. Be aware that LWG support for a particular resolution can quickly change if new viewpoints or killer examples are presented in subsequent discussions.
For the most current official version of this document see http://www.open-std.org/jtc1/sc22/wg21/. Requests for further information about this document should include the document number above, reference ISO/IEC 14882:1998(E), and be submitted to Information Technology Industry Council (ITI), 1250 Eye Street NW, Washington, DC 20005.
Public information as to how to obtain a copy of the C++ Standard, join the standards committee, submit an issue, or comment on an issue can be found in the comp.std.c++ FAQ. Public discussion of C++ Standard related issues occurs on news:comp.std.c++.
For committee members, files available on the committee's private web site include the HTML version of the Standard itself. HTML hyperlinks from this issues list to those files will only work for committee members who have downloaded them into the same disk directory as the issues list files.
New - The issue has not yet been reviewed by the LWG. Any Proposed Resolution is purely a suggestion from the issue submitter, and should not be construed as the view of LWG.
Open - The LWG has discussed the issue but is not yet ready to move the issue forward. There are several possible reasons for open status:
A Proposed Resolution for an open issue is still not be construed as the view of LWG. Comments on the current state of discussions are often given at the end of open issues in an italic font. Such comments are for information only and should not be given undue importance.
Dup - The LWG has reached consensus that the issue is a duplicate of another issue, and will not be further dealt with. A Rationale identifies the duplicated issue's issue number.
NAD - The LWG has reached consensus that the issue is not a defect in the Standard, and the issue is ready to forward to the full committee as a proposed record of response. A Rationale discusses the LWG's reasoning.
Review - Exact wording of a Proposed Resolution is now available for review on an issue for which the LWG previously reached informal consensus.
Tentatively Ready - The issue has been reviewed online, but not in a meeting, and some support has been formed for the proposed resolution. Tentatively Ready issues may be moved to Ready and forwarded to full committee within the same meeting. Unlike Ready issues they will be reviewed in subcommittee prior to forwarding to full committee.
Ready - The LWG has reached consensus that the issue is a defect in the Standard, the Proposed Resolution is correct, and the issue is ready to forward to the full committee for further action as a Defect Report (DR).
DR - (Defect Report) - The full J16 committee has voted to forward the issue to the Project Editor to be processed as a Potential Defect Report. The Project Editor reviews the issue, and then forwards it to the WG21 Convenor, who returns it to the full committee for final disposition. This issues list accords the status of DR to all these Defect Reports regardless of where they are in that process.
TC - (Technical Corrigenda) - The full WG21 committee has voted to accept the Defect Report's Proposed Resolution as a Technical Corrigenda. Action on this issue is thus complete and no further action is possible under ISO rules.
WP - (Working Paper) - The proposed resolution has not been accepted as a Technical Corrigendum, but the full WG21 committee has voted to apply the Defect Report's Proposed Resolution to the working paper.
RR - (Record of Response) - The full WG21 committee has determined that this issue is not a defect in the Standard. Action on this issue is thus complete and no further action is possible under ISO rules.
Future - In addition to the regular status, the LWG believes that this issue should be revisited at the next revision of the standard. It is usually paired with NAD.
Issues are always given the status of New when they first appear on the issues list. They may progress to Open or Review while the LWG is actively working on them. When the LWG has reached consensus on the disposition of an issue, the status will then change to Dup, NAD, or Ready as appropriate. Once the full J16 committee votes to forward Ready issues to the Project Editor, they are given the status of Defect Report ( DR). These in turn may become the basis for Technical Corrigenda (TC), or are closed without action other than a Record of Response (RR ). The intent of this LWG process is that only issues which are truly defects in the Standard move to the formal ISO DR status.
Section: 22.2.2.1.2 [lib.facet.num.get.virtuals] Status: Open Submitter: Nathan Myers Date: 6 Aug 1998
The current description of numeric input does not account for the possibility of overflow. This is an implicit result of changing the description to rely on the definition of scanf() (which fails to report overflow), and conflicts with the documented behavior of traditional and current implementations.
Users expect, when reading a character sequence that results in a value unrepresentable in the specified type, to have an error reported. The standard as written does not permit this.
Further comments from Dietmar:
I don't feel comfortable with the proposed resolution to issue 23: It kind of simplifies the issue to much. Here is what is going on:
Currently, the behavior of numeric overflow is rather counter intuitive and hard to trace, so I will describe it briefly:
Further discussion from Redmond:
The basic problem is that we've defined our behavior, including our error-reporting behavior, in terms of C90. However, C90's method of reporting overflow in scanf is not technically an "input error". The strto_* functions are more precise.
There was general consensus that failbit should be set upon overflow. We considered three options based on this:
Straw poll: (1) 5; (2) 0; (3) 8.
Proposed resolution:
Discussed at Lillehammer. General outline of what we want the solution to look like: we want to say that overflow is an error, and provide a way to distinguish overflow from other kinds of errors. Choose candidate field the same way scanf does, but don't describe the rest of the process in terms of format. If a finite input field is too large (positive or negative) to be represented as a finite value, then set failbit and assign the nearest representable value. Bill will provide wording.
Section: 23.2.5 [lib.vector.bool] Status: Open Submitter: AFNOR Date: 7 Oct 1998
vector<bool> is not a container as its reference and pointer types are not references and pointers.
Also it forces everyone to have a space optimization instead of a speed one.
See also: 99-0008 == N1185 Vector<bool> is Nonconforming, Forces Optimization Choice.
Proposed resolution:
[In Santa Cruz the LWG felt that this was Not A Defect.]
[In Dublin many present felt that failure to meet Container requirements was a defect. There was disagreement as to whether or not the optimization requirements constituted a defect.]
[The LWG looked at the following resolutions in some detail:
* Not A Defect.
* Add a note explaining that vector<bool> does not meet
Container requirements.
* Remove vector<bool>.
* Add a new category of container requirements which
vector<bool> would meet.
* Rename vector<bool>.
No alternative had strong, wide-spread, support and every alternative
had at least one "over my dead body" response.
There was also mention of a transition scheme something like (1) add
vector_bool and deprecate vector<bool> in the next standard. (2)
Remove vector<bool> in the following standard.]
[Modifying container requirements to permit returning proxies (thus allowing container requirements conforming vector<bool>) was also discussed.]
[It was also noted that there is a partial but ugly workaround in that vector<bool> may be further specialized with a customer allocator.]
[Kona: Herb Sutter presented his paper J16/99-0035==WG21/N1211, vector<bool>: More Problems, Better Solutions. Much discussion of a two step approach: a) deprecate, b) provide replacement under a new name. LWG straw vote on that: 1-favor, 11-could live with, 2-over my dead body. This resolution was mentioned in the LWG report to the full committee, where several additional committee members indicated over-my-dead-body positions.]
Discussed at Lillehammer. General agreement that we should deprecate vector<bool> and introduce this functionality under a different name, e.g. bit_vector. This might make it possible to remove the vector<bool> specialization in the standard that comes after C++0x. There was also a suggestion that in C++0x we could additional say that it's implementation defined whether vector<bool> refers to the specialization or to the primary template, but there wasn't general agreement that this was a good idea.
We need a paper for the new bit_vector class.
Section: 18.2.1 [lib.limits] Status: Open Submitter: Stephen Cleary Date: 21 Dec 1999
In some places in this section, the terms "fundamental types" and "scalar types" are used when the term "arithmetic types" is intended. The current usage is incorrect because void is a fundamental type and pointers are scalar types, neither of which should have specializations of numeric_limits.
Proposed resolution:
[Lillehammer: it remains true that numeric_limits is using imprecise language. However, none of the proposals for changed wording are clearer. A redesign of numeric_limits is needed, but this is more a task than an open issue.]
Section: 23.1.2 [lib.associative.reqmts] Status: Open Submitter: Andrew Koenig Date: 30 Apr 2000
If mm is a multimap and p is an iterator into the multimap, then mm.insert(p, x) inserts x into mm with p as a hint as to where it should go. Table 69 claims that the execution time is amortized constant if the insert winds up taking place adjacent to p, but does not say when, if ever, this is guaranteed to happen. All it says it that p is a hint as to where to insert.
The question is whether there is any guarantee about the relationship between p and the insertion point, and, if so, what it is.
I believe the present state is that there is no guarantee: The user can supply p, and the implementation is allowed to disregard it entirely.
Additional comments from Nathan:
The vote [in Redmond] was on whether to elaborately specify the use of
the hint, or to require behavior only if the value could be inserted
adjacent to the hint. I would like to ensure that we have a chance to
vote for a deterministic treatment: "before, if possible, otherwise
after, otherwise anywhere appropriate", as an alternative to the
proposed "before or after, if possible, otherwise [...]".
Proposed resolution:
In table 69 "Associative Container Requirements" in 23.1.2 [lib.associative.reqmts], in the row for a.insert(p, t), change
iterator p is a hint pointing to where the insert should start to search.
to
insertion adjacent to iterator p is preferred if more than one insertion point is valid.
and change
logarithmic in general, but amortized constant if t is inserted right after p.
to
logarithmic in general, but amortized constant if t is inserted adjacent to iterator p.
[Toronto: there was general agreement that this is a real defect: when inserting an element x into a multiset that already contains several copies of x, there is no way to know whether the hint will be used. The proposed resolution was that the new element should always be inserted as close to the hint as possible. So, for example, if there is a subsequence of equivalent values, then providing a.begin() as the hint means that the new element should be inserted before the subsequence even if a.begin() is far away. JC van Winkel supplied precise wording for this proposed resolution, and also for an alternative resolution in which hints are only used when they are adjacent to the insertion point.]
[Copenhagen: the LWG agreed to the original proposed resolution, in which an insertion hint would be used even when it is far from the insertion point. This was contingent on seeing a reference implementation showing that it is possible to implement this requirement without loss of efficiency. John Potter provided such a reference implementation.]
[Redmond: The LWG was reluctant to adopt the proposal that emerged from Copenhagen: it seemed excessively complicated, and went beyond fixing the defect that we identified in Toronto. PJP provided the new wording described in this issue. Nathan agrees that we shouldn't adopt the more detailed semantics, and notes: "we know that you can do it efficiently enough with a red-black tree, but there are other (perhaps better) balanced tree techniques that might differ enough to make the detailed semantics hard to satisfy."]
[Curaçao: Nathan should give us the alternative wording he suggests so the LWG can decide between the two options.]
[Lillehammer: The LWG previously rejected the more detailed semantics, because it seemed more loike a new feature than like defect fixing. We're now more sympathetic to it, but we (especially Bill) are still worried about performance. N1780 describes a naive algorithm, but it's not clear whether there is a non-naive implementation. Is it possible to implement this as efficently as the current version of insert?]
[Post Lillehammer: N1780 updated in post meeting mailing with feedback from Lillehammer with more information regarding performance. ]
Section: 19.1 [lib.std.exceptions] Status: Open Submitter: Dave Abrahams Date: 01 Aug 2000
Many of the standard exception types which implementations are required to throw are constructed with a const std::string& parameter. For example:
19.1.5 Class out_of_range [lib.out.of.range]
namespace std {
class out_of_range : public logic_error {
public:
explicit out_of_range(const string& what_arg);
};
}
1 The class out_of_range defines the type of objects thrown as excep-
tions to report an argument value not in its expected range.
out_of_range(const string& what_arg);
Effects:
Constructs an object of class out_of_range.
Postcondition:
strcmp(what(), what_arg.c_str()) == 0.
There are at least two problems with this:
There may be no cure for (1) other than changing the interface to out_of_range, though one could reasonably argue that (1) is not a defect. Personally I don't care that much if out-of-memory is reported when I only have 20 bytes left, in the case when out_of_range would have been reported. People who use exception-specifications might care a lot, though.
There is a cure for (2), but it isn't completely obvious. I think a note for implementors should be made in the standard. Avoiding possible termination in this case shouldn't be left up to chance. The cure is to use a reference-counted "string" implementation in the exception object. I am not necessarily referring to a std::string here; any simple reference-counting scheme for a NTBS would do.
Further discussion, in email:
...I'm not so concerned about (1). After all, a library implementation can add const char* constructors as an extension, and users don't need to avail themselves of the standard exceptions, though this is a lame position to be forced into. FWIW, std::exception and std::bad_alloc don't require a temporary basic_string.
...I don't think the fixed-size buffer is a solution to the problem,
strictly speaking, because you can't satisfy the postcondition
strcmp(what(), what_arg.c_str()) == 0
For all values of what_arg (i.e. very long values). That means that
the only truly conforming solution requires a dynamic allocation.
Further discussion, from Redmond:
The most important progress we made at the Redmond meeting was realizing that there are two separable issues here: the const string& constructor, and the copy constructor. If a user writes something like throw std::out_of_range("foo"), the const string& constructor is invoked before anything gets thrown. The copy constructor is potentially invoked during stack unwinding.
The copy constructor is a more serious problem, becuase failure during stack unwinding invokes terminate. The copy constructor must be nothrow. Curaçao: Howard thinks this requirement may already be present.
The fundamental problem is that it's difficult to get the nothrow requirement to work well with the requirement that the exception objects store a string of unbounded size, particularly if you also try to make the const string& constructor nothrow. Options discussed include:
(Not all of these options are mutually exclusive.)
Proposed resolution:
Rationale:
Throwing a bad_alloc while trying to construct a message for another exception-derived class is not necessarily a bad thing. And the bad_alloc constructor already has a no throw spec on it (18.4.2.1).
Future:
All involved would like to see const char* constructors added, but this should probably be done for C++0X as opposed to a DR.
I believe the no throw specs currently decorating these functions could be improved by some kind of static no throw spec checking mechanism (in a future C++ language). As they stand, the copy constructors might fail via a call to unexpected. I think what is intended here is that the copy constructors can't fail.
[Pre-Sydney: reopened at the request of Howard Hinnant. Post-Redmond: James Kanze noticed that the copy constructors of exception-derived classes do not have nothrow clauses. Those classes have no copy constructors declared, meaning the compiler-generated implicit copy constructors are used, and those compiler-generated constructors might in principle throw anything.]
Section: 20.1.5 [lib.allocator.requirements] Status: Open Submitter: Matt Austern Date: 22 Aug 2000
From lib-7752:
I've been assuming (and probably everyone else has been assuming) that allocator instances have a particular property, and I don't think that property can be deduced from anything in Table 32.
I think we have to assume that allocator type conversion is a homomorphism. That is, if x1 and x2 are of type X, where X::value_type is T, and if type Y is X::template rebind<U>::other, then Y(x1) == Y(x2) if and only if x1 == x2.
Further discussion: Howard Hinnant writes, in lib-7757:
I think I can prove that this is not provable by Table 32. And I agree it needs to be true except for the "and only if". If x1 != x2, I see no reason why it can't be true that Y(x1) == Y(x2). Admittedly I can't think of a practical instance where this would happen, or be valuable. But I also don't see a need to add that extra restriction. I think we only need:
if (x1 == x2) then Y(x1) == Y(x2)
If we decide that == on allocators is transitive, then I think I can prove the above. But I don't think == is necessarily transitive on allocators. That is:
Given x1 == x2 and x2 == x3, this does not mean x1 == x3.
Example:
x1 can deallocate pointers from: x1, x2, x3
x2 can deallocate pointers from: x1, x2, x4
x3 can deallocate pointers from: x1, x3
x4 can deallocate pointers from: x2, x4x1 == x2, and x2 == x4, but x1 != x4
Proposed resolution:
[Toronto: LWG members offered multiple opinions. One opinion is that it should not be required that x1 == x2 implies Y(x1) == Y(x2), and that it should not even be required that X(x1) == x1. Another opinion is that the second line from the bottom in table 32 already implies the desired property. This issue should be considered in light of other issues related to allocator instances.]
Section: 25.1.1 [lib.alg.foreach] Status: Open Submitter: Angelika Langer Date: 03 Jan 2001
The specification of the for_each algorithm does not have a "Requires" section, which means that there are no restrictions imposed on the function object whatsoever. In essence it means that I can provide any function object with arbitrary side effects and I can still expect a predictable result. In particular I can expect that the function object is applied exactly last - first times, which is promised in the "Complexity" section.
I don't see how any implementation can give such a guarantee without imposing requirements on the function object.
Just as an example: consider a function object that removes elements from the input sequence. In that case, what does the complexity guarantee (applies f exactly last - first times) mean?
One can argue that this is obviously a nonsensical application and a theoretical case, which unfortunately it isn't. I have seen programmers shooting themselves in the foot this way, and they did not understand that there are restrictions even if the description of the algorithm does not say so.
Proposed resolution:
[Lillehammer: This is more general than for_each. We don't want the function object in transform invalidiating iterators either. There should be a note somewhere in clause 17 (17, not 25) saying that user code operating on a range may not invalidate iterators unless otherwise specified. Bill will provide wording.]
Section: 24.1.4 [lib.bidirectional.iterators], 24.1.5 [lib.random.access.iterators] Status: Open Submitter: John Potter Date: 22 Jan 2001
In section 24.1.4 [lib.bidirectional.iterators], Table 75 gives the return type of *r-- as convertible to T. This is not consistent with Table 74 which gives the return type of *r++ as T&. *r++ = t is valid while *r-- = t is invalid.
In section 24.1.5 [lib.random.access.iterators], Table 76 gives the return type of a[n] as convertible to T. This is not consistent with the semantics of *(a + n) which returns T& by Table 74. *(a + n) = t is valid while a[n] = t is invalid.
Discussion from the Copenhagen meeting: the first part is uncontroversial. The second part, operator[] for Random Access Iterators, requires more thought. There are reasonable arguments on both sides. Return by value from operator[] enables some potentially useful iterators, e.g. a random access "iota iterator" (a.k.a "counting iterator" or "int iterator"). There isn't any obvious way to do this with return-by-reference, since the reference would be to a temporary. On the other hand, reverse_iterator takes an arbitrary Random Access Iterator as template argument, and its operator[] returns by reference. If we decided that the return type in Table 76 was correct, we would have to change reverse_iterator. This change would probably affect user code.
History: the contradiction between reverse_iterator and the Random Access Iterator requirements has been present from an early stage. In both the STL proposal adopted by the committee (N0527==94-0140) and the STL technical report (HPL-95-11 (R.1), by Stepanov and Lee), the Random Access Iterator requirements say that operator[]'s return value is "convertible to T". In N0527 reverse_iterator's operator[] returns by value, but in HPL-95-11 (R.1), and in the STL implementation that HP released to the public, reverse_iterator's operator[] returns by reference. In 1995, the standard was amended to reflect the contents of HPL-95-11 (R.1). The original intent for operator[] is unclear.
In the long term it may be desirable to add more fine-grained iterator requirements, so that access method and traversal strategy can be decoupled. (See "Improved Iterator Categories and Requirements", N1297 = 01-0011, by Jeremy Siek.) Any decisions about issue 299 should keep this possibility in mind.
Further discussion: I propose a compromise between John Potter's resolution, which requires T& as the return type of a[n], and the current wording, which requires convertible to T. The compromise is to keep the convertible to T for the return type of the expression a[n], but to also add a[n] = t as a valid expression. This compromise "saves" the common case uses of random access iterators, while at the same time allowing iterators such as counting iterator and caching file iterators to remain random access iterators (iterators where the lifetime of the object returned by operator*() is tied to the lifetime of the iterator).
Note that the compromise resolution necessitates a change to reverse_iterator. It would need to use a proxy to support a[n] = t.
Note also there is one kind of mutable random access iterator that will no longer meet the new requirements. Currently, iterators that return an r-value from operator[] meet the requirements for a mutable random access iterartor, even though the expression a[n] = t will only modify a temporary that goes away. With this proposed resolution, a[n] = t will be required to have the same operational semantics as *(a + n) = t.
Proposed resolution:
In section 24.1.4 [lib.bidirectdional.iterators], change the return type in table 75 from "convertible to T" to T&.
In section 24.1.5 [lib.random.access.iterators], change the operational semantics for a[n] to " the r-value of a[n] is equivalent to the r-value of *(a + n)". Add a new row in the table for the expression a[n] = t with a return type of convertible to T and operational semantics of *(a + n) = t.
[Lillehammer: Real problem, but should be addressed as part of iterator redesign]
Section: 27.6 [lib.iostream.format] Status: Open Submitter: Martin Sebor Date: 19 Mar 2001
The descriptions of the constructors of basic_istream<>::sentry (27.6.1.1.2 [lib.istream::sentry]) and basic_ostream<>::sentry (27.6.2.3 [lib.ostream::sentry]) do not explain what the functions do in case an exception is thrown while they execute. Some current implementations allow all exceptions to propagate, others catch them and set ios_base::badbit instead, still others catch some but let others propagate.
The text also mentions that the functions may call setstate(failbit) (without actually saying on what object, but presumably the stream argument is meant). That may have been fine for basic_istream<>::sentry prior to issue 195, since the function performs an input operation which may fail. However, issue 195 amends 27.6.1.1.2 [lib.istream::sentry], p2 to clarify that the function should actually call setstate(failbit | eofbit), so the sentence in p3 is redundant or even somewhat contradictory.
The same sentence that appears in 27.6.2.3 [lib.ostream::sentry], p3 doesn't seem to be very meaningful for basic_istream<>::sentry which performs no input. It is actually rather misleading since it would appear to guide library implementers to calling setstate(failbit) when os.tie()->flush(), the only called function, throws an exception (typically, it's badbit that's set in response to such an event).
Additional comments from Martin, who isn't comfortable with the current proposed resolution (see c++std-lib-11530)
The istream::sentry ctor says nothing about how the function deals with exemptions (27.6.1.1.2, p1 says that the class is responsible for doing "exception safe"(*) prefix and suffix operations but it doesn't explain what level of exception safety the class promises to provide). The mockup example of a "typical implementation of the sentry ctor" given in 27.6.1.1.2, p6, removed in ISO/IEC 14882:2003, doesn't show exception handling, either. Since the ctor is not classified as a formatted or unformatted input function, the text in 27.6.1.1, p1 through p4 does not apply. All this would seem to suggest that the sentry ctor should not catch or in any way handle exceptions thrown from any functions it may call. Thus, the typical implementation of an istream extractor may look something like [1].
The problem with [1] is that while it correctly sets ios::badbit if an exception is thrown from one of the functions called from the sentry ctor, if the sentry ctor reaches EOF while extracting whitespace from a stream that has eofbit or failbit set in exceptions(), it will cause an ios::failure to be thrown, which will in turn cause the extractor to set ios::badbit.
The only straightforward way to prevent this behavior is to move the definition of the sentry object in the extractor above the try block (as suggested by the example in 22.2.8, p9 and also indirectly supported by 27.6.1.3, p1). See [2]. But such an implementation will allow exceptions thrown from functions called from the ctor to freely propagate to the caller regardless of the setting of ios::badbit in the stream object's exceptions().
So since neither [1] nor [2] behaves as expected, the only possible solution is to have the sentry ctor catch exceptions thrown from called functions, set badbit, and propagate those exceptions if badbit is also set in exceptions(). (Another solution exists that deals with both kinds of sentries, but the code is non-obvious and cumbersome -- see [3].)
Please note that, as the issue points out, current libraries do not behave consistently, suggesting that implementors are not quite clear on the exception handling in istream::sentry, despite the fact that some LWG members might feel otherwise. (As documented by the parenthetical comment here: http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2003/n1480.html#309)
Also please note that those LWG members who in Copenhagen felt that "a sentry's constructor should not catch exceptions, because sentries should only be used within (un)formatted input functions and that exception handling is the responsibility of those functions, not of the sentries," as noted here http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2001/n1310.html#309 would in effect be either arguing for the behavior described in [1] or for extractors implemented along the lines of [3].
The original proposed resolution (Revision 25 of the issues list) clarifies the role of the sentry ctor WRT exception handling by making it clear that extractors (both library or user-defined) should be implemented along the lines of [2] (as opposed to [1]) and that no exception thrown from the callees should propagate out of either function unless badbit is also set in exceptions().
[1] Extractor that catches exceptions thrown from sentry:
struct S { long i; };
istream& operator>> (istream &strm, S &s)
{
ios::iostate err = ios::goodbit;
try {
const istream::sentry guard (strm, false);
if (guard) {
use_facet<num_get<char> >(strm.getloc ())
.get (istreambuf_iterator<char>(strm),
istreambuf_iterator<char>(),
strm, err, s.i);
}
}
catch (...) {
bool rethrow;
try {
strm.setstate (ios::badbit);
rethrow = false;
}
catch (...) {
rethrow = true;
}
if (rethrow)
throw;
}
if (err)
strm.setstate (err);
return strm;
}
[2] Extractor that propagates exceptions thrown from sentry:
istream& operator>> (istream &strm, S &s)
{
istream::sentry guard (strm, false);
if (guard) {
ios::iostate err = ios::goodbit;
try {
use_facet<num_get<char> >(strm.getloc ())
.get (istreambuf_iterator<char>(strm),
istreambuf_iterator<char>(),
strm, err, s.i);
}
catch (...) {
bool rethrow;
try {
strm.setstate (ios::badbit);
rethrow = false;
}
catch (...) {
rethrow = true;
}
if (rethrow)
throw;
}
if (err)
strm.setstate (err);
}
return strm;
}
[3] Extractor that catches exceptions thrown from sentry but doesn't set badbit if the exception was thrown as a result of a call to strm.clear().
istream& operator>> (istream &strm, S &s)
{
const ios::iostate state = strm.rdstate ();
const ios::iostate except = strm.exceptions ();
ios::iostate err = std::ios::goodbit;
bool thrown = true;
try {
const istream::sentry guard (strm, false);
thrown = false;
if (guard) {
use_facet<num_get<char> >(strm.getloc ())
.get (istreambuf_iterator<char>(strm),
istreambuf_iterator<char>(),
strm, err, s.i);
}
}
catch (...) {
if (thrown && state & except)
throw;
try {
strm.setstate (ios::badbit);
thrown = false;
}
catch (...) {
thrown = true;
}
if (thrown)
throw;
}
if (err)
strm.setstate (err);
return strm;
}
[Pre-Berlin] Reopened at the request of Paolo Carlini and Steve Clamage.
Proposed resolution:
Rationale:
The LWG agrees there is minor variation between implementations, but believes that it doesn't matter. This is a rarely used corner case. There is no evidence that this has any commercial importance or that it causes actual portability problems for customers trying to write code that runs on multiple implementations.
Section: 27.6.1.3 [lib.istream.unformatted] Status: Open Submitter: Howard Hinnant Date: 09 Oct 2001
I think we have a defect.
According to lwg issue 60 which is now a dr, the description of seekg in 27.6.1.3 [lib.istream.unformatted] paragraph 38 now looks like:
Behaves as an unformatted input function (as described in 27.6.1.3, paragraph 1), except that it does not count the number of characters extracted and does not affect the value returned by subsequent calls to gcount(). After constructing a sentry object, if fail() != true, executes rdbuf()>pubseekpos( pos).
And according to lwg issue 243 which is also now a dr, 27.6.1.3, paragraph 1 looks like:
Each unformatted input function begins execution by constructing an object of class sentry with the default argument noskipws (second) argument true. If the sentry object returns true, when converted to a value of type bool, the function endeavors to obtain the requested input. Otherwise, if the sentry constructor exits by throwing an exception or if the sentry object returns false, when converted to a value of type bool, the function returns without attempting to obtain any input. In either case the number of extracted characters is set to 0; unformatted input functions taking a character array of non-zero size as an argument shall also store a null character (using charT()) in the first location of the array. If an exception is thrown during input then ios::badbit is turned on in *this'ss error state. If (exception()&badbit)!= 0 then the exception is rethrown. It also counts the number of characters extracted. If no exception has been thrown it ends by storing the count in a member object and returning the value specified. In any event the sentry object is destroyed before leaving the unformatted input function.
And finally 27.6.1.1.2/5 says this about sentry:
If, after any preparation is completed, is.good() is true, ok_ != false otherwise, ok_ == false.
So although the seekg paragraph says that the operation proceeds if !fail(), the behavior of unformatted functions says the operation proceeds only if good(). The two statements are contradictory when only eofbit is set. I don't think the current text is clear which condition should be respected.
Further discussion from Redmond:
PJP: It doesn't seem quite right to say that seekg is "unformatted". That makes specific claims about sentry that aren't quite appropriate for seeking, which has less fragile failure modes than actual input. If we do really mean that it's unformatted input, it should behave the same way as other unformatted input. On the other hand, "principle of least surprise" is that seeking from EOF ought to be OK.
Pre-Berlin: Paolo points out several problems with the proposed resolution in Ready state:
Proposed resolution:
Change 27.6.1.3 [lib.istream.unformatted] to:
Behaves as an unformatted input function (as described in 27.6.1.3, paragraph 1), except that it does not count the number of characters extracted, does not affect the value returned by subsequent calls to gcount(), and does not examine the value returned by the sentry object. After constructing a sentry object, if fail() != true, executes rdbuf()->pubseekpos(pos). In case of success, the function calls clear(). In case of failure, the function calls setstate(failbit) (which may throw ios_base::failure).
[Lillehammer: Matt provided wording.]
Rationale:
In C, fseek does clear EOF. This is probably what most users would expect. We agree that having eofbit set should not deter a seek, and that a successful seek should clear eofbit. Note that fail() is true only if failbit or badbit is set, so using !fail(), rather than good(), satisfies this goal.
Section: 22.2.1.5 [lib.locale.codecvt] Status: Open Submitter: Martin Sebor Date: 30 Aug 2002
It seems that the descriptions of codecvt do_in() and do_out() leave sufficient room for interpretation so that two implementations of codecvt may not work correctly with the same filebuf. Specifically, the following seems less than adequately specified:
Finally, the conditions described at the end of 22.2.1.5.2 [lib.locale.codecvt.virtuals], p4 don't seem to be possible:
"A return value of partial, if (from_next == from_end), indicates that either the destination sequence has not absorbed all the available destination elements, or that additional source elements are needed before another destination element can be produced."
If the value is partial, it's not clear to me that (from_next ==from_end) could ever hold if there isn't enough room in the destination buffer. In order for (from_next==from_end) to hold, all characters in that range must have been successfully converted (according to 22.2.1.5.2 [lib.locale.codecvt.virtuals], p2) and since there are no further source characters to convert, no more room in the destination buffer can be needed.
It's also not clear to me that (from_next==from_end) could ever hold if additional source elements are needed to produce another destination character (not element as incorrectly stated in the text). partial is returned if "not all source characters have been converted" according to Table 53, which also implies that (from_next==from) does NOT hold.
Could it be that the intended qualifying condition was actually (from_next != from_end), i.e., that the sentence was supposed to read
"A return value of partial, if (from_next != from_end),..."
which would make perfect sense, since, as far as I understand it, partial can only occur if (from_next != from_end)?
Proposed resolution:
[Lillehammer: Defer for the moment, but this really needs to be fixed. Right now, the description of codecvt is too vague for it to be a useful contract between providers and clients of codecvt facets. (Note that both vendors and users can be both providers and clients of codecvt facets.) The major philosophical issue is whether the standard should only describe mappings that take a single wide character to multiple narrow characters (and vice versa), or whether it should describe fully general N-to-M conversions. When the original standard was written only the former was contemplated, but today, in light of the popularity of utf8 and utf16, that doesn't seem sufficient for C++0x. Bill supports general N-to-M conversions; we need to make sure Martin and Howard agree.]
Section: 17 [lib.library] Status: Open Submitter: Matt Austern Date: 23 Oct 2002
Many function templates have parameters that are passed by value; a typical example is find_if's pred parameter in 25.1.2 [lib.alg.find]. Are the corresponding template parameters (Predicate in this case) implicitly required to be CopyConstructible, or does that need to be spelled out explicitly?
This isn't quite as silly a question as it might seem to be at first sight. If you call find_if in such a way that template argument deduction applies, then of course you'll get call by value and you need to provide a copy constructor. If you explicitly provide the template arguments, however, you can force call by reference by writing something like find_if<my_iterator, my_predicate&>. The question is whether implementation are required to accept this, or whether this is ill-formed because my_predicate& is not CopyConstructible.
The scope of this problem, if it is a problem, is unknown. Function object arguments to generic algorithms in clauses 25 [lib.algorithms] and 26 [lib.numerics] are obvious examples. A review of the whole library is necessary.
Proposed resolution:
[ This is really two issues. First, predicates are typically passed by value but we don't say they must be Copy Constructible. They should be. Second: is specialization allowed to transform value arguments into references? References aren't copy constructible, so this should not be allowed. ]
Section: 26.2 [lib.complex.numbers] Status: Open Submitter: Gabriel Dos Reis Date: 8 Nov 2002
The absence of explicit description of std::complex<T> layout makes it imposible to reuse existing software developed in traditional languages like Fortran or C with unambigous and commonly accepted layout assumptions. There ought to be a way for practitioners to predict with confidence the layout of std::complex<T> whenever T is a numerical datatype. The absence of ways to access individual parts of a std::complex<T> object as lvalues unduly promotes severe pessimizations. For example, the only way to change, independently, the real and imaginary parts is to write something like
complex<T> z; // ... // set the real part to r z = complex<T>(r, z.imag()); // ... // set the imaginary part to i z = complex<T>(z.real(), i);
At this point, it seems appropriate to recall that a complex number is, in effect, just a pair of numbers with no particular invariant to maintain. Existing practice in numerical computations has it that a complex number datatype is usually represented by Cartesian coordinates. Therefore the over-encapsulation put in the specification of std::complex<> is not justified.
Proposed resolution:
Add the following requirements to 26.2 [lib.complex.numbers] as 26.2/4:
If z is an lvalue expression of type cv std::complex<T> then
- the expression reinterpret_cast<cv T(&)[2]>(z) is well-formed; and
- reinterpret_cast<cvT(&)[2]>(z)[0]designates the real part of z; and
- reinterpret_cast<cvT(&)[2]>(z)[1]designates the imaginary part of z.
Moreover, if a is an expression of pointer type cv complex<T>* and the expression a[i] is well-defined for an integer expression i then:
- reinterpret_cast<cvT*>(a)[2+i] designates the real part of a[i]; and
- reinterpret_cast<cv T*>(a)[2+i+1] designates the imaginary part of a[i].
In the header synopsis in 26.2.1 [lib.complex.synopsis], replace
template<class T> T real(const complex<T>&); template<class T> T imag(const complex<T>&);
with
template<class T> const T& real(const complex<T>&); template<class T> T& real( complex<T>&); template<class T> const T& imag(const complex<T>&); template<class T> T& imag( complex<T>&);
In 26.2.7 [lib.complex.value.ops] paragraph 1, change
template<class T> T real(const complex<T>&);
to
template<class T> const T& real(const complex<T>&); template<class T> T& real( complex<T>&);
and change the Returns clause to "Returns: The real part of x
.In 26.2.7 [lib.complex.value.ops] paragraph 2, change
template<class T> T imag(const complex<T>&);
to
template<class T> const T& imag(const complex<T>&); template<class T> T& imag( complex<T>&);
and change the Returns clause to "Returns: The imaginary part of x
.[Kona: The layout guarantee is absolutely necessary for C compatibility. However, there was disagreement about the other part of this proposal: retrieving elements of the complex number as lvalues. An alternative: continue to have real() and imag() return rvalues, but add set_real() and set_imag(). Straw poll: return lvalues - 2, add setter functions - 5. Related issue: do we want reinterpret_cast as the interface for converting a complex to an array of two reals, or do we want to provide a more explicit way of doing it? Howard will try to resolve this issue for the next meeting.]
[pre-Sydney: Howard summarized the options in n1589.]
Rationale:
The LWG believes that C99 compatibility would be enough justification for this change even without other considerations. All existing implementations already have the layout proposed here.
Section: 27.6.2.5.1 [lib.ostream.formatted.reqmts] Status: Open Submitter: Martin Sebor Date: 27 Dec 2002
There is a contradiction in Formatted output about what bit is supposed to be set if the formatting fails. On sentence says it's badbit and another that it's failbit.
27.6.2.5.1, p1 says in the Common Requirements on Formatted output functions:
... If the generation fails, then the formatted output function
does setstate(ios::failbit), which might throw an exception.
27.6.2.5.2, p1 goes on to say this about Arithmetic Inserters:
... The formatting conversion occurs as if it performed the following code fragment:
bool failed =
use_facet<num_put<charT,ostreambuf_iterator<charT,traits>
> >
(getloc()).put(*this, *this, fill(), val). failed();
... If failed is true then does setstate(badbit) ...
The original intent of the text, according to Jerry Schwarz (see c++std-lib-10500), is captured in the following paragraph:
In general "badbit" should mean that the stream is unusable because of some underlying failure, such as disk full or socket closure; "failbit" should mean that the requested formatting wasn't possible because of some inconsistency such as negative widths. So typically if you clear badbit and try to output something else you'll fail again, but if you clear failbit and try to output something else you'll succeed.
In the case of the arithmetic inserters, since num_put cannot report failure by any means other than exceptions (in response to which the stream must set badbit, which prevents the kind of recoverable error reporting mentioned above), the only other detectable failure is if the iterator returned from num_put returns true from failed().
Since that can only happen (at least with the required iostream specializations) under such conditions as the underlying failure referred to above (e.g., disk full), setting badbit would seem to be the appropriate response (indeed, it is required in 27.6.2.5.2, p1). It follows that failbit can never be directly set by the arithmetic (it can only be set by the sentry object under some unspecified conditions).
The situation is different for other formatted output functions which can fail as a result of the streambuf functions failing (they may do so by means other than exceptions), and which are then required to set failbit.
The contradiction, then, is that ostream::operator<<(int) will set badbit if the disk is full, while operator<<(ostream&, char) will set failbit under the same conditions. To make the behavior consistent, the Common requirements sections for the Formatted output functions should be changed as proposed below.
Proposed resolution:
[Kona: There's agreement that this is a real issue. What we decided at Kona: 1. An error from the buffer (which can be detected either directly from streambuf's member functions or by examining a streambuf_iterator) should always result in badbit getting set. 2. There should never be a circumstance where failbit gets set. That represents a formatting error, and there are no circumstances under which the output facets are specified as signaling a formatting error. (Even more so for string output that for numeric because there's nothing to format.) If we ever decide to make it possible for formatting errors to exist then the facets can signal the error directly, and that should go in clause 22, not clause 27. 3. The phrase "if generation fails" is unclear and should be eliminated. It's not clear whether it's intended to mean a buffer error (e.g. a full disk), a formatting error, or something else. Most people thought it was supposed to refer to buffer errors; if so, we should say so. Martin will provide wording.]
Rationale:
Section: 23.3.5.1 [lib.bitset.cons] Status: Open Submitter: Martin Sebor Date: 5 Jan 2003
23.3.5.1, p6 [lib.bitset.cons] talks about a generic character having the value of 0 or 1 but there is no definition of what that means for charT other than char and wchar_t. And even for those two types, the values 0 and 1 are not actually what is intended -- the values '0' and '1' are. This, along with the converse problem in the description of to_string() in 23.3.5.2, p33, looks like a defect remotely related to DR 303.
http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/lwg-defects.html#303
23.3.5.1:
-6- An element of the constructed string has value zero if the
corresponding character in str, beginning at position pos,
is 0. Otherwise, the element has the value one.
23.3.5.2:
-33- Effects: Constructs a string object of the appropriate
type and initializes it to a string of length N characters.
Each character is determined by the value of its
corresponding bit position in *this. Character position N
?- 1 corresponds to bit position zero. Subsequent decreasing
character positions correspond to increasing bit positions.
Bit value zero becomes the character 0, bit value one becomes
the character 1.
Also note the typo in 23.3.5.1, p6: the object under construction is a bitset, not a string.
Proposed resolution:
Change the constructor's function declaration immediately before 23.3.5.1 [lib.bitset.cons] p3 to:
template <class charT, class traits, class Allocator>
explicit
bitset(const basic_string<charT, traits, Allocator>& str,
typename basic_string<charT, traits, Allocator>::size_type pos = 0,
typename basic_string<charT, traits, Allocator>::size_type n =
basic_string<charT, traits, Allocator>::npos,
charT zero = charT('0'), charT one = charT('1'))
Change the first two sentences of 23.3.5.1 [lib.bitset.cons] p6 to: "An element of the constructed string has value 0 if the corresponding character in str, beginning at position pos, is zero. Otherwise, the element has the value 1.
Change the text of the second sentence in 23.3.5.1, p5 to read: "The function then throws invalid_argument if any of the rlen characters in str beginning at position pos is other than zero or one. The function uses traits::eq() to compare the character values."
Change the declaration of the to_string member function immediately before 23.3.5.2 [lib.bitset.members] p33 to:
template <class charT, class traits, class Allocator>
basic_string<charT, traits, Allocator>
to_string(charT zero = charT('0'), charT one = charT('1')) const;
Change the last sentence of 23.3.5.2 [lib.bitset.members] p33 to: "Bit value 0 becomes the character zero, bit value 1 becomes the character one.
Change 23.3.5.3 [lib.bitset.operators] p8 to:
Returns:
os << x.template to_string<charT,traits,allocator<charT> >(
use_facet<ctype<charT> >(os.getloc()).widen('0'),
use_facet<ctype<charT> >(os.getloc()).widen('1'));
Rationale:
There is a real problem here: we need the character values of '0' and '1', and we have no way to get them since strings don't have imbued locales. In principle the "right" solution would be to provide an extra object, either a ctype facet or a full locale, which would be used to widen '0' and '1'. However, there was some discomfort about using such a heavyweight mechanism. The proposed resolution allows those users who care about this issue to get it right.
We fix the inserter to use the new arguments. Note that we already fixed the analogous problem with the extractor in issue 303.
Section: 27.6.2.3 [lib.ostream::sentry] Status: Open Submitter: Martin Sebor Date: 5 Jan 2003
17.4.4.8, p3 prohibits library dtors from throwing exceptions.
27.6.2.3, p4 says this about the ostream::sentry dtor:
-4- If ((os.flags() & ios_base::unitbuf) && !uncaught_exception())
is true, calls os.flush().
27.6.2.6, p7 that describes ostream::flush() says:
-7- If rdbuf() is not a null pointer, calls rdbuf()->pubsync().
If that function returns ?-1 calls setstate(badbit) (which
may throw ios_base::failure (27.4.4.3)).
That seems like a defect, since both pubsync() and setstate() can throw an exception.
Proposed resolution:
[ The contradiction is real. Clause 17 says destructors may never throw exceptions, and clause 27 specifies a destructor that does throw. In principle we might change either one. We're leaning toward changing clause 17: putting in an "unless otherwise specified" clause, and then putting in a footnote saying the sentry destructor is the only one that can throw. PJP suggests specifying that sentry::~sentry() should internally catch any exceptions it might cause. ]
Section: 27.6.2.3 [lib.ostream::sentry] Status: Open Submitter: Martin Sebor Date: 5 Jan 2003
While reviewing unformatted input member functions of istream for their behavior when they encounter end-of-file during input I found that the requirements vary, sometimes unexpectedly, and in more than one case even contradict established practice (GNU libstdc++ 3.2, IBM VAC++ 6.0, STLPort 4.5, SunPro 5.3, HP aCC 5.38, Rogue Wave libstd 3.1, and Classic Iostreams).
The following unformatted input member functions set eofbit if they encounter an end-of-file (this is the expected behavior, and also the behavior of all major implementations):
basic_istream<charT, traits>&
get (char_type*, streamsize, char_type);
Also sets failbit if it fails to extract any characters.
basic_istream<charT, traits>&
get (char_type*, streamsize);
Also sets failbit if it fails to extract any characters.
basic_istream<charT, traits>&
getline (char_type*, streamsize, char_type);
Also sets failbit if it fails to extract any characters.
basic_istream<charT, traits>&
getline (char_type*, streamsize);
Also sets failbit if it fails to extract any characters.
basic_istream<charT, traits>&
ignore (int, int_type);
basic_istream<charT, traits>&
read (char_type*, streamsize);
Also sets failbit if it encounters end-of-file.
streamsize readsome (char_type*, streamsize);
The following unformated input member functions set failbit but not eofbit if they encounter an end-of-file (I find this odd since the functions make it impossible to distinguish a general failure from a failure due to end-of-file; the requirement is also in conflict with all major implementation which set both eofbit and failbit):
int_type get();
basic_istream<charT, traits>&
get (char_type&);
These functions only set failbit of they extract no characters, otherwise they don't set any bits, even on failure (I find this inconsistency quite unexpected; the requirement is also in conflict with all major implementations which set eofbit whenever they encounter end-of-file):
basic_istream<charT, traits>&
get (basic_streambuf<charT, traits>&, char_type);
basic_istream<charT, traits>&
get (basic_streambuf<charT, traits>&);
This function sets no bits (all implementations except for STLport and Classic Iostreams set eofbit when they encounter end-of-file):
int_type peek ();
Proposed resolution:
Informally, what we want is a global statement of intent saying that eofbit gets set if we trip across EOF, and then we can take away the specific wording for individual functions. A full review is necessary. The wording currently in the standard is a mishmash, and changing it on an individual basis wouldn't make things better. Dietmar will do this work.
Section: 20.1.5 [lib.allocator.requirements] Status: Open Submitter: Markus Mauhart Date: 27 Feb 2003
I think that in par2 of 20.1.5 [lib.allocator.requirements] the last two lines of table 32 contain two incorrect type casts. The lines are ...
a.construct(p,t) Effect: new((void*)p) T(t) a.destroy(p) Effect: ((T*)p)?->~T()
.... with the prerequisits coming from the preceding two paragraphs, especially from table 31:
alloc<T> a ;// an allocator for T
alloc<T>::pointer p ;// random access iterator
// (may be different from T*)
alloc<T>::reference r = *p;// T&
T const& t ;
For that two type casts ("(void*)p" and "(T*)p") to be well-formed this would require then conversions to T* and void* for all alloc<T>::pointer, so it would implicitely introduce extra requirements for alloc<T>::pointer, additionally to the only current requirement (being a random access iterator).
Proposed resolution:
"(void*)p" should be replaced with "(void*)&*p" and that "((T*)p)?->" should be replaced with "(*p)." or with "(&*p)->".
Note: Actually I would prefer to replace "((T*)p)?->dtor_name" with "p?->dtor_name", but AFAICS this is not possible cause of an omission in 13.5.6 [over.ref] (for which I have filed another DR on 29.11.2002).
[Kona: The LWG thinks this is somewhere on the border between Open and NAD. The intend is clear: construct constructs an object at the location p. It's reading too much into the description to think that literally calling new is required. Tweaking this description is low priority until we can do a thorough review of allocators, and, in particular, allocators with non-default pointer types.]
Section: 24.1 [lib.iterator.requirements] Status: Open Submitter: Nathan Myers Date: 3 June 2003
I've been discussing iterator semantics with Dave Abrahams, and a surprise has popped up. I don't think this has been discussed before.
24.1 [lib.iterator.requirements] says that the only operation that can be performed on "singular" iterator values is to assign a non-singular value to them. (It doesn't say they can be destroyed, and that's probably a defect.) Some implementations have taken this to imply that there is no need to initialize the data member of a reverse_iterator<> in the default constructor. As a result, code like
std::vector<std::reverse_iterator<char*> > v(7); v.reserve(1000);
invokes undefined behavior, because it must default-initialize the vector elements, and then copy them to other storage. Of course many other vector operations on these adapters are also left undefined, and which those are is not reliably deducible from the standard.
I don't think that 24.1 was meant to make standard-library iterator types unsafe. Rather, it was meant to restrict what operations may be performed by functions which take general user- and standard iterators as arguments, so that raw pointers would qualify as iterators. However, this is not clear in the text, others have come to the opposite conclusion.
One question is whether the standard iterator adaptors have defined copy semantics. Another is whether they have defined destructor semantics: is
{ std::vector<std::reverse_iterator<char*> > v(7); }
undefined too?
Note this is not a question of whether algorithms are allowed to rely on copy semantics for arbitrary iterators, just whether the types we actually supply support those operations. I believe the resolution must be expressed in terms of the semantics of the adapter's argument type. It should make clear that, e.g., the reverse_iterator<T> constructor is actually required to execute T(), and so copying is defined if the result of T() is copyable.
Issue 235, which defines reverse_iterator's default constructor more precisely, has some relevance to this issue. However, it is not the whole story.
The issue was whether
reverse_iterator() { }
is allowed, vs.
reverse_iterator() : current() { }
The difference is when T is char*, where the first leaves the member uninitialized, and possibly equal to an existing pointer value, or (on some targets) may result in a hardware trap when copied.
8.5 paragraph 5 seems to make clear that the second is required to satisfy DR 235, at least for non-class Iterator argument types.
But that only takes care of reverse_iterator, and doesn't establish a policy for all iterators. (The reverse iterator adapter was just an example.) In particular, does my function
template <typename Iterator> void f() { std::vector<Iterator> v(7); }
evoke undefined behavior for some conforming iterator definitions? I think it does, now, because vector<> will destroy those singular iterator values, and that's explicitly disallowed.
24.1 shouldn't give blanket permission to copy all singular iterators, because then pointers wouldn't qualify as iterators. However, it should allow copying of that subset of singular iterator values that are default-initialized, and it should explicitly allow destroying any iterator value, singular or not, default-initialized or not.
Related issue: 407
Proposed resolution:
[ We don't want to require all singular iterators to be copyable, because that is not the case for pointers. However, default construction may be a special case. Issue: is it really default construction we want to talk about, or is it something like value initialization? We need to check with core to see whether default constructed pointers are required to be copyable; if not, it would be wrong to impose so strict a requirement for iterators. ]
Section: 18.2.2 [lib.c.limits] Status: Open Submitter: Martin Sebor Date: 18 Sep 2003
Given two overloads of the function foo(), one taking an argument of type
int and the other taking a long, which one will the call foo(LONG_MAX)
resolve to? The expected answer should be foo(long), but whether that
is true depends on the #defintion of the LONG_MAX macro, specifically
its type. This issue is about the fact that the type of these macros
is not actually required to be the same as the the type each respective
limit.
Section 18.2.2 of the C++ Standard does not specify the exact types of
the XXX_MIN and XXX_MAX macros #defined in the <climits> and <limits.h>
headers such as INT_MAX and LONG_MAX and instead defers to the C standard.
Section 5.2.4.2.1, p1 of the C standard specifies that "The values [of
these constants] shall be replaced by constant expressions suitable for use
in #if preprocessing directives. Moreover, except for CHAR_BIT and MB_LEN_MAX,
the following shall be replaced by expressions that have the same type as
would an expression that is an object of the corresponding type converted
according to the integer promotions."
The "corresponding type converted according to the integer promotions" for
LONG_MAX is, according to 6.4.4.1, p5 of the C standard, the type of long
converted to the first of the following set of types that can represent it:
int, long int, long long int. So on an implementation where (sizeof(long)
== sizeof(int)) this type is actually int, while on an implementation where
(sizeof(long) > sizeof(int)) holds this type will be long.
This is not an issue in C since the type of the macro cannot be detected
by any conforming C program, but it presents a portability problem in C++
where the actual type is easily detectable by overload resolution.
Proposed resolution:
[Kona: the LWG does not believe this is a defect. The C macro definitions are what they are; we've got a better mechanism, std::numeric_limits, that is specified more precisely than the C limit macros. At most we should add a nonnormative note recommending that users who care about the exact types of limit quantities should use <limits> instead of <climits>.]
Section: 22.2.1.1.2 [lib.locale.ctype.virtuals] Status: Open Submitter: Martin Sebor Date: 18 Sep 2003
The Effects and Returns clauses of the do_widen() member function of the ctype facet fail to specify the behavior of the function on failure. That the function may not be able to simply cast the narrow character argument to the type of the result since doing so may yield the wrong value for some wchar_t encodings. Popular implementations of ctype<wchar_t> that use mbtowc() and UTF-8 as the native encoding (e.g., GNU glibc) will fail when the argument's MSB is set. There is no way for the the rest of locale and iostream to reliably detect this failure.
Proposed resolution:
[Kona: This is a real problem. Widening can fail. It's unclear what the solution should be. Returning WEOF works for the wchar_t specialization, but not in general. One option might be to add a default, like narrow. But that's an incompatible change. Using traits::eof might seem like a good idea, but facets don't have access to traits (a recurring problem). We could have widen throw an exception, but that's a scary option; existing library components aren't written with the assumption that widen can throw.]
Section: 27.4.2.1.6 [lib.ios::Init] Status: Open Submitter: Martin Sebor Date: 18 Sep 2003
The dtor of the ios_base::Init object is supposed to call flush() on the 6 standard iostream objects cout, cerr, clog, wcout, wcerr, and wclog. This call may cause an exception to be thrown.
17.4.4.8, p3 prohibits all library destructors from throwing exceptions.
The question is: What should this dtor do if one or more of these calls to flush() ends up throwing an exception? This can happen quite easily if one of the facets installed in the locale imbued in the iostream object throws.
Proposed resolution:
[Kona: We probably can't do much better than what we've got, so the LWG is leaning toward NAD. At the point where the standard stream objects are being cleaned up, the usual error reporting mechanism are all unavailable. And exception from flush at this point will definitely cause problems. A quality implementation might reasonably swallow the exception, or call abort, or do something even more drastic.]
Section: 27.6.1.1.2 [lib.istream::sentry] Status: Open Submitter: Martin Sebor Date: 18 Sep 2003
27.6.1.1.2, p2 says that istream::sentry ctor prepares for input if is.good() is true. p4 then goes on to say that the ctor sets the sentry::ok_ member to true if the stream state is good after any preparation. 27.6.1.2.1, p1 then says that a formatted input function endeavors to obtain the requested input if the sentry's operator bool() returns true. Given these requirements, no formatted extractor should ever set failbit if the initial stream rdstate() == eofbit. That is contrary to the behavior of all implementations I tested. The program below prints out eof = 1, fail = 0 eof = 1, fail = 1 on all of them.
#include <sstream>
#include <cstdio>
int main()
{
std::istringstream strm ("1");
int i = 0;
strm >> i;
std::printf ("eof = %d, fail = %d\n",
!!strm.eof (), !!strm.fail ());
strm >> i;
std::printf ("eof = %d, fail = %d\n",
!!strm.eof (), !!strm.fail ());
}
Comments from Jerry Schwarz (c++std-lib-11373):
Jerry Schwarz wrote:
I don't know where (if anywhere) it says it in the standard, but the
formatted extractors are supposed to set failbit if they don't extract
any characters. If they didn't then simple loops like
while (cin >> x);
would loop forever.
Further comments from Martin Sebor:
The question is which part of the extraction should prevent this from happening
by setting failbit when eofbit is already set. It could either be the sentry
object or the extractor. It seems that most implementations have chosen to
set failbit in the sentry [...] so that's the text that will need to be
corrected.
Pre Berlin: This issue is related to 342. If the sentry sets failbit when it finds eofbit already set, then you can never seek away from the end of stream.
Proposed resolution:
Kona: Possibly NAD. If eofbit is set then good() will return false. We then set ok to false. We believe that the sentry's constructor should always set failbit when ok is false, and we also think the standard already says that. Possibly it could be clearer.
Section: 27.5.2.1 [lib.streambuf.cons] Status: Open Submitter: Martin Sebor Date: 18 Sep 2003
The reflector thread starting with c++std-lib-11346 notes that the class template basic_streambuf, along with basic_stringbuf and basic_filebuf, is copy-constructible but that the semantics of the copy constructors are not defined anywhere. Further, different implementations behave differently in this respect: some prevent copy construction of objects of these types by declaring their copy ctors and assignment operators private, others exhibit undefined behavior, while others still give these operations well-defined semantics.
Note that this problem doesn't seem to be isolated to just the three types mentioned above. A number of other types in the library section of the standard provide a compiler-generated copy ctor and assignment operator yet fail to specify their semantics. It's believed that the only types for which this is actually a problem (i.e. types where the compiler-generated default may be inappropriate and may not have been intended) are locale facets. See issue 439.
Proposed resolution:
27.5.2 [lib.streambuf]: Add into the synopsis, public section, just above the destructor declaration:
basic_streambuf(const basic_streambuf& sb); basic_streambuf& operator=(const basic_streambuf& sb);
Insert after 27.5.2.1, paragraph 2:
basic_streambuf(const basic_streambuf& sb);Constructs a copy of sb.
Postcondtions:
eback() == sb.eback() gptr() == sb.gptr() egptr() == sb.egptr() pbase() == sb.pbase() pptr() == sb.pptr() epptr() == sb.epptr() getloc() == sb.getloc()basic_streambuf& operator=(const basic_streambuf& sb);Assigns the data members of sb to this.
Postcondtions:
eback() == sb.eback() gptr() == sb.gptr() egptr() == sb.egptr() pbase() == sb.pbase() pptr() == sb.pptr() epptr() == sb.epptr() getloc() == sb.getloc()Returns: *this.
27.7.1 [lib.stringbuf]:
Option A:Option B:Insert into the basic_stringbuf synopsis in the private section:
basic_stringbuf(const basic_stringbuf&); // not defined basic_stringbuf& operator=(const basic_stringbuf&); // not defined
Insert into the basic_stringbuf synopsis in the public section:
basic_stringbuf(const basic_stringbuf& sb); basic_stringbuf& operator=(const basic_stringbuf& sb);27.7.1.1, insert after paragraph 4:
basic_stringbuf(const basic_stringbuf& sb);Constructs an independent copy of sb as if with sb.str(), and with the openmode that sb was constructed with.
Postcondtions:
str() == sb.str() gptr() - eback() == sb.gptr() - sb.eback() egptr() - eback() == sb.egptr() - sb.eback() pptr() - pbase() == sb.pptr() - sb.pbase() getloc() == sb.getloc()Note: The only requirement on epptr() is that it point beyond the initialized range if an output sequence exists. There is no requirement that epptr() - pbase() == sb.epptr() - sb.pbase().
basic_stringbuf& operator=(const basic_stringbuf& sb);After assignment the basic_stringbuf has the same state as if it were initially copy constructed from sb, except that the basic_stringbuf is allowed to retain any excess capacity it might have, which may in turn effect the value of epptr().
27.8.1.1 [lib.filebuf]
Insert at the bottom of the basic_filebuf synopsis:
private: basic_filebuf(const basic_filebuf&); // not defined basic_filebuf& operator=(const basic_filebuf&); // not defined
[Kona: this is an issue for basic_streambuf itself and for its derived classes. We are leaning toward allowing basic_streambuf to be copyable, and specifying its precise semantics. (Probably the obvious: copying the buffer pointers.) We are less sure whether the streambuf derived classes should be copyable. Howard will write up a proposal.]
[Sydney: Dietmar presented a new argument against basic_streambuf being copyable: it can lead to an encapsulation violation. Filebuf inherits from streambuf. Now suppose you inhert a my_hijacking_buf from streambuf. You can copy the streambuf portion of a filebuf to a my_hijacking_buf, giving you access to the pointers into the filebuf's internal buffer. Perhaps not a very strong argument, but it was strong enough to make people nervous. There was weak preference for having streambuf not be copyable. There was weak preference for having stringbuf not be copyable even if streambuf is. Move this issue to open for now. ]
Rationale:
27.5.2 [lib.streambuf]: The proposed basic_streambuf copy constructor and assignment operator are the same as currently implied by the lack of declarations: public and simply copies the data members. This resolution is not a change but a clarification of the current standard.
27.7.1 [lib.stringbuf]: There are two reasonable options: A) Make basic_stringbuf not copyable. This is likely the status-quo of current implementations. B) Reasonable copy semantics of basic_stringbuf can be defined and implemented. A copyable basic_streambuf is arguably more useful than a non-copyable one. This should be considered as new functionality and not the fixing of a defect. If option B is chosen, ramifications from issue 432 are taken into account.
27.8.1.1 [lib.filebuf]: There are no reasonable copy semantics for basic_filebuf.
Section: 17.4.3.1 [lib.reserved.names] Status: Open Submitter: Martin Sebor Date: 18 Sep 2003
It has been suggested that 17.4.3.1, p1 may or may not allow programs to explicitly specialize members of standard templates on user-defined types. The answer to the question might have an impact where library requirements are given using the "as if" rule. I.e., if programs are allowed to specialize member functions they will be able to detect an implementation's strict conformance to Effects clauses that describe the behavior of the function in terms of the other member function (the one explicitly specialized by the program) by relying on the "as if" rule.
Proposed resolution:
Add the following sentence immediately after the text of 17.4.3.1 [lib.reserved.names], p1:
The behavior of a program that declares explicit specializations of any members of class templates or explicit specializations of any member templates of classes or class templates defined in this library is undefined.
[Kona: straw poll was 6-1 that user programs should not be allowed to specialize individual member functions of standard library class templates, and that doing so invokes undefined behavior. Post-Kona: Martin provided wording.]
[Sydney: The LWG agrees that the standard shouldn't permit users to specialize individual member functions unless they specialize the whole class, but we're not sure these words say what we want them to; they could be read as prohibiting the specialization of any standard library class templates. We need to consult with CWG to make sure we use the right wording.]
Section: 27 [lib.input.output] Status: Open Submitter: Martin Sebor Date: 18 Sep 2003
A third party test suite tries to exercise istream::ignore(N) with a negative value of N and expects that the implementation will treat N as if it were 0. Our implementation asserts that (N >= 0) holds and aborts the test.
I can't find anything in section 27 that prohibits such values but I don't see what the effects of such calls should be, either (this applies to a number of unformatted input functions as well as some member functions of the basic_streambuf template).
Proposed resolution:
I propose that we add to each function in clause 27 that takes an argument, say N, of type streamsize a Requires clause saying that "N >= 0." The intent is to allow negative streamsize values in calls to precision() and width() but disallow it in calls to streambuf::sgetn(), istream::ignore(), or ostream::write().
[Kona: The LWG agreed that this is probably what we want. However, we need a review to find all places where functions in clause 27 take arguments of type streamsize that shouldn't be allowed to go negative. Martin will do that review.]
Section: 17.3.1.1 [lib.structure.summary] Status: Open Submitter: Martin Sebor Date: 18 Sep 2003
The text in 17.3.1.1, p1 says:
"Paragraphs labelled "Note(s):" or "Example(s):" are informative, other
paragraphs are normative."
The library section makes heavy use of paragraphs labeled "Notes(s),"
some of which are clearly intended to be normative (see list 1), while
some others are not (see list 2). There are also those where the intent
is not so clear (see list 3).
List 1 -- Examples of (presumably) normative Notes:
20.4.1.1, p3, 20.4.1.1, p10, 21.3.1, p11, 22.1.1.2, p11, 23.2.1.3, p2,
25.3.7, p3, 26.2.6, p14a, 27.5.2.4.3, p7.
List 2 -- Examples of (presumably) informative Notes:
18.4.1.3, p3, 21.3.5.6, p14, 22.2.1.5.2, p3, 25.1.1, p4, 26.2.5, p1,
27.4.2.5, p6.
List 3 -- Examples of Notes that are not clearly either normative
or informative:
22.1.1.2, p8, 22.1.1.5, p6, 27.5.2.4.5, p4.
None of these lists is meant to be exhaustive.
Proposed resolution:
[Definitely a real problem. The big problem is there's material that doesn't quite fit any of the named paragraph categories (e.g. Effects). Either we need a new kind of named paragraph, or we need to put more material in unnamed paragraphs jsut after the signature. We need to talk to the Project Editor about how to do this. ]
Section: 22.2.2.1.2 [lib.facet.num.get.virtuals] Status: Open Submitter: Martin Sebor Date: 18 Sep 2003
The requirements specified in Stage 2 and reiterated in the rationale of DR 221 (and echoed again in DR 303) specify that num_get<charT>:: do_get() compares characters on the stream against the widened elements of "012...abc...ABCX+-"
An implementation is required to allow programs to instantiate the num_get template on any charT that satisfies the requirements on a user-defined character type. These requirements do not include the ability of the character type to be equality comparable (the char_traits template must be used to perform tests for equality). Hence, the num_get template cannot be implemented to support any arbitrary character type. The num_get template must either make the assumption that the character type is equality-comparable (as some popular implementations do), or it may use char_traits<charT> to do the comparisons (some other popular implementations do that). This diversity of approaches makes it difficult to write portable programs that attempt to instantiate the num_get template on user-defined types.
Proposed resolution:
[Kona: the heart of the problem is that we're theoretically supposed to use traits classes for all fundamental character operations like assignment and comparison, but facets don't have traits parameters. This is a fundamental design flaw and it appears all over the place, not just in this one place. It's not clear what the correct solution is, but a thorough review of facets and traits is in order. The LWG considered and rejected the possibility of changing numeric facets to use narrowing instead of widening. This may be a good idea for other reasons (see issue 459), but it doesn't solve the problem raised by this issue. Whether we use widen or narrow the num_get facet still has no idea which traits class the user wants to use for the comparison, because only streams, not facets, are passed traits classes. The standard does not require that two different traits classes with the same char_type must necessarily have the same behavior.]
Informally, one possibility: require that some of the basic character operations, such as eq, lt, and assign, must behave the same way for all traits classes with the same char_type. If we accept that limitation on traits classes, then the facet could reasonably be required to use char_traits<charT>
.Section: 26.3.2.4 [lib.valarray.sub] Status: Open Submitter: Martin Sebor Date: 18 Sep 2003
The standard fails to specify the behavior of valarray::operator[](slice) and other valarray subset operations when they are passed an "invalid" slice object, i.e., either a slice that doesn't make sense at all (e.g., slice (0, 1, 0) or one that doesn't specify a valid subset of the valarray object (e.g., slice (2, 1, 1) for a valarray of size 1).
Proposed resolution:
[Kona: the LWG believes that invalid slices should invoke undefined behavior. Valarrays are supposed to be designed for high performance, so we don't want to require specific checking. We need wording to express this decision.]
Section: 20.1.5 [lib.allocator.requirements], 25 [lib.algorithms] Status: Open Submitter: Matt Austern Date: 20 Sep 2003
Clause 20.1.5 [lib.allocator.requirements] paragraph 4 says that implementations are permitted to supply containers that are unable to cope with allocator instances and that container implementations may assume that all instances of an allocator type compare equal. We gave implementers this latitude as a temporary hack, and eventually we want to get rid of it. What happens when we're dealing with allocators that don't compare equal?
In particular: suppose that v1 and v2 are both objects of type vector<int, my_alloc> and that v1.get_allocator() != v2.get_allocator(). What happens if we write v1.swap(v2)? Informally, three possibilities:
1. This operation is illegal. Perhaps we could say that an implementation is required to check and to throw an exception, or perhaps we could say it's undefined behavior.
2. The operation performs a slow swap (i.e. using three invocations of operator=, leaving each allocator with its original container. This would be an O(N) operation.
3. The operation swaps both the vectors' contents and their allocators. This would be an O(1) operation. That is:
my_alloc a1(...);
my_alloc a2(...);
assert(a1 != a2);
vector<int, my_alloc> v1(a1);
vector<int, my_alloc> v2(a2);
assert(a1 == v1.get_allocator());
assert(a2 == v2.get_allocator());
v1.swap(v2);
assert(a1 == v2.get_allocator());
assert(a2 == v1.get_allocator());
Proposed resolution:
[Kona: This is part of a general problem. We need a paper saying how to deal with unequal allocators in general.]
[pre-Sydney: Howard argues for option 3 in n1599.]