Document number:  N2900=09-0090
Date: 2009-06-16
Project: Programming Language C++, Library Working Group
Reply-to: Beman Dawes <bdawes at acm.org>

Ensuring Certain C++0x Features "just work" - Revision 1
or, addressing CD1 comments UK 78 and UK 79

Introduction
Revision history
Summary of proposed changes
FAQ
    Why not also implicitly #include <iterator_concepts>?
Additional discussion
Proposed wording
Acknowledgements

Introduction

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.

Summary of proposed changes

Revision history

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.

FAQ

Why not also implicitly #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.

Additional discussion

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.

Proposed wording

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 ) statement

 is 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 the expression, and begin-expr and end-expr are determined as follows:

[ 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-qualified std::initializer_list<E> for some type E, 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 std::initializer_list is not predefined; if the header <initializer_list> is not included prior to a use of std::initializer_list — even an implicit use in which the type is not named (7.1.6.4) — the program is ill-formed. [Note: The template std::initializer_list is predefined ([support.initlist]). --end note]

Acknowledgements

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.