Document number: | N2900=09-0090 |
Date: | 2009-06-16 |
Project: | Programming Language C++, Library Working Group |
Reply-to: | Beman Dawes <bdawes at acm.org> |
Introduction
Revision history
Summary of proposed changes
FAQ
Why not also
implicitly #include <iterator_concepts>
?
Additional discussion
Proposed wording
Acknowledgements
CD1 comments UK 78 and UK 79 essentially ask that the range-based for statement "just work". Consider the range-based for example given in the WP:
[ Example:
int array[5] = { 1, 2, 3, 4, 5 }; for (int& x : array) x *= 2;—end example ]
As the WP is currently specified, this example will not compile! It is missing:
#include <iterator_concepts>
Independent of UK 78/79, a lengthy discussion of range-based for loops
over arrays on the C++ committee's library reflector found a number of LWG members who
believe the array case should "just-work" without having to include <iterator_concepts>
.
This is seen as similar to:
... new int ... // no need for <new>
... sizeof(MyType) ... // no need for <cstddef> to get std::size_t
... = nullptr; // no need for <cstddef> to get nullptr
As Peter Dimov said in message c++std-lib-23406:
It is precisely the required inclusion of
<iterator_concepts>
that makes arrays a special case ... For all other types, the fact that I can use the type at all means that I can use it in a [range-based] for-loop as well (because the header that defines the type would include<iterator_concepts>
for me).Arrays, however, are available without an #include, so the [range-based] for-loop should also be available; the alternative is an inconsistency, a special case from the user point of view (not from specification point of view, but this is an implementation detail as far as the user is concerned).
UK 79 also mentions the case of initializer lists. An example comes from Bjarne Stroustrup's C++0x FAQ:
for (const auto x : { 1,2,3,5,8,13,21,34 })...;
For this to compile given the current WP wording, both <initializer_list>
and
<iterator_concepts>
must be included. So again, code that should
"just-work" because it need rely only on features of the core language doesn't work
without explicit #includes of library headers.
This proposal, together with library issue 1001, addresses all of the above concerns, and resolves the CD1 UK 78 and UK 79 comments.
<initializer_list>
in all translation
units. This allows std::initializer_list
to "just work", and
reflects its special role as part of the core language.<initializer_list>
so that it as no dependencies on
other headers and includes no other headers. This minimizes the cost of
including it in all translation units.<iterator_concepts>
and so do not require users to include
headers (unless, of course, the types of
objects being iterated over require headers). Header <iterator_concepts>
is still required for uses of std::Range
.N2900 - Revision 1. Changed "possibly
cv-qualified" to "possibly const-qualified" in two places to ensure that
behavior is the same regardless of whether or not <iterator_concepts>
is included. Suggested by Daniel Krügler.
N2872 Initial proposal.
#include <iterator_concepts>
?Header <iterator_concepts>
has a dependency on header
<concepts>
, so both would have to be included. That's an unacceptably
large cost for the many translation units that will need neither header. It also
unnecessarily couples the core language to portions of the library not actually
required for language support, and that is considered a poor design practice.
With the proposed resolution of library issue
1001, <iterator_concepts>
never has to be explicitly
included for the range-based for statement to work with standard library types.
In contrast, <iterator_list>
as modified by this proposal is very small,
has no dependencies, contains nothing except template initializer_list
,
is tightly coupled to core language support, and is critical to the range-based
for statement "just working".
Implicitly including <iterator_concepts>
has been looked at numerous times by multiple C++ committee sub-groups and soundly rejected each time.
Gabriel Dos Reis in c++std-lib-23383:
We don't need <iterator_concepts> to tell us what
for (v: ary) {
// ...
}does when 'ary' is a C-array.
<iterator_concepts>
is not part of the model. It is an implementation detail that should not leak.
Peter Dimov in message c++std-lib-23408 points out:
... the observable behavior of the for-loop [with the proposed change] always matches its description in terms of the Range concept (it always "uses" the Range concept). The fact that 'for' on a C array does not require the [explicit] definition of Range is an implementation detail and not detectable. You can't add a concept map without also having Range defined (right?), so there is no way you can observe the difference.
UK 79 comment:
The definition of for (for-range-declaration : expression) statement is expanded in terms which require a Range concept, and the program is ill-formed if
<iterator_concepts>
isn't included. For users, iterating through old-fashioned arrays, this is a sledge-hammer to crack a nut and compares poorly with other languages. It's also not possible to implement this without adversely impacting the freestanding definition in 17.6.2.4.
Text to be removed is shown in red, text to be added is shown in green. Underbars are not shown for additions because the many underscores in the affected text become unreadable. Changes shown in gray are editorial changes recommended for consideration by the project editor.
Change 18.9 [support.initlist], Initializer lists, paragraph 1,, as indicated:
The header
<initializer_list>
defines one type. Header<initializer_list>
shall be implicitly included at the start of each translation unit. Header<initializer_list>
shall not include other C++ standard library headers. [Note: Thus#include
<initializer_list>
has no effect. --end note]
Change 18.9 [support.initlist], Initializer lists, Header <initializer_list> synopsis, as indicated:
namespace std { template<ObjectType E> class initializer_list { public: typedef E value_type; typedef const E& reference; typedef const E& const_reference; typedef size_t size_type; typedef const E* iterator; typedef const E* const_iterator; initializer_list(); size_t size() const; // number of elements const E* begin() const; // first element const E* end() const; // one past the last element };template<typename T> concept_map Range<initializer_list<T> > see below; template<typename T> concept_map Range<const initializer_list<T> > see below;}
Add initializer_list concept maps to 24.2 [iterator.concepts], Iterator concepts, Header <iterator_concepts> synopsis:
namespace std { concept Iterator<typename X> see below; // 24.2.2, input iterators: concept InputIterator<typename X> see below; // 24.2.3, output iterators: auto concept OutputIterator<typename X, typename Value> see below; // 24.2.4, forward iterators: concept ForwardIterator<typename X> see below; // 24.2.5, bidirectional iterators: concept BidirectionalIterator<typename X> see below; // 24.2.6, random access iterators: concept RandomAccessIterator<typename X> see below; template<ObjectType T> concept_map RandomAccessIterator<T*> see below; template<ObjectType T> concept_map RandomAccessIterator<const T*> see below; // 24.2.7, shuffle iterators: auto concept ShuffleIterator<typename X> see below; // 24.2.8, ranges: concept Range<typename T> see below; template<class T, size_t N> concept_map Range<T[N]> see below; template<typename T> concept_map Range<initializer_list<T> > see below; template<typename T> concept_map Range<const initializer_list<T> > see below; }
Move the two concept maps below from 18.9.3 [support.initlist.concept], Initializer_list concept maps, to the end of section 24.2.8 [iterator.concepts.range] Ranges:
template<typename T> concept_map Range<initializer_list<T> > { typedef const T* iterator; iterator begin(initializer_list<T> r) { return r.begin(); } iterator end(initializer_list<T> r) { return r.end(); } } template<typename T> concept_map Range<const initializer_list<T> > { typedef const T* iterator; iterator begin(initializer_list<T> r) { return r.begin(); } iterator end(initializer_list<T> r) { return r.end(); } }
Change 6.5.4 [stmt.ranged], The range-based for statement, as indicated:
The range-based for statement
for (
for-range-declaration:
expression)
statementis equivalent to
{ auto && __range = ( expression ); for ( auto __begin =std::Range<_RangeT>::begin(__range)begin-expr, __end =std::Range<_RangeT>::end(__range)end-expr; __begin != __end; ++__begin ) { for-range-declaration = *__begin; statement } }where
__range
,__begin
, and__end
are variables defined for exposition only, and_RangeT
is the type of theexpression
, andbegin-expr
andend-expr
are determined as follows:
- If the header
<iterator_concepts>
([iterator.concepts]) is not has been included prior to a this use of the range-based for statement,begin-expr
andend-expr
arestd::Range<_RangeT>::begin(__range)
andstd::Range<_RangeT>::end(__range)
, respectively.
- Otherwise, if
_RangeT
is a possibly const-qualified array of a complete type,begin-expr
andend-expr
are__range
and__range+__bound
, respectively, where__bound
is the array bound.
- Otherwise, if
_RangeT
is a possibly const-qualifiedstd::initializer_list
,begin-expr
andend-expr
are__range.begin()
and__range.end()
, respectively. [Note: The templatestd::initializer_list
is predefined ([support.initlist]). --end note]
- Otherwise, the program is ill-formed.
[ Example:
int array[5] = { 1, 2, 3, 4, 5 }; for (int& x : array) x *= 2;—end example ]
Change 17.6.4.2 [res.on.headers], Headers, paragraph 1, as indicated:
A C++ header may include other C++ headers unless otherwise specified.
Change 8.5.4 [dcl.init.list], List-initialization, paragraph 3, as indicated:
A constructor is an initializer-list constructor if its first parameter is of type
std::initializer_list<E>
([support.initlist]) or reference to possibly cv-qualifiedstd::initializer_list<E>
for some typeE
, and either there are no other parameters or else all other parameters have default arguments (8.3.6). [ Note: Initializer-list constructors are favored over other constructors in list-initialization (13.3.1.7).—end note ]The template[Note: The templatestd::initializer_list
is not predefined; if the header<
initializer_list
>
is not included prior to a use ofstd::initializer_list
— even an implicit use in which the type is not named (7.1.6.4) — the program is ill-formed.std::initializer_list
is predefined ([support.initlist]). --end note]
This proposal parallels
N2814, Fixing Freestanding, by Martin Tasker and Jan van Bergen,
as regards range-based for statements and header <initializer_list>
.
Martin and Jan provided helpful suggestions for this proposal.
James Widman identified several deficiencies in drafts of this proposal and provided fixes for them.
Steve Adamczyk, Alberto Ganesh Barbati, Walter E Brown, Gabriel Dos Reis, Doug Gregor, Daniel Krügler, Jens Maurer, Thorsten Ottosen, Bjarne Stroustrup, Herb Sutter, and James Widman participated in discussions, reviewed drafts, and suggested improvements.