How does a “for each” loop in C++ know the length of an array

后端 未结 4 1546
终归单人心
终归单人心 2021-01-13 07:15

I was looking at the following example from http://www.cplusplus.com/doc/tutorial/arrays/ and I couldn\'t figure out how the 2nd for loop worked. How can the for loop know w

相关标签:
4条回答
  • 2021-01-13 07:50

    The reason this works is that the for loop effectively1 uses std::begin and std::end. Those, in turn, work, because they provide overloads specifically for built-in arrays, something like this:

    template <class T, size_t N>
    T *begin(T (&array)[N]) {
        return array;
    }
    
    template <class T, size_t N>
    T *end(T (&array)[N]) {
        return array+N;
    }
    

    Although it (apparently) hadn't been realized before the original (1998) C++ standard was published, this technique doesn't require any language features beyond those available in C++98. C++11 codified the technique and puts it to use.

    Since, in this case, the parameter is specified as a reference to an array, type deduction will only succeed when the parameter really is an array. In the case of std::begin, there are also versions that support other parameter types and use (for example) the begin() and end() members of collections, if that type is matched.


    1. "Effectively" in this case meaning there are some circumstances in which a range-based for loop uses begin and end, and others in which they're not. If you're into technicalities, they're not used for arrays, but a similar computation is done directly. Likewise, for container types that have begin and end members, those are used directly. If neither of those is true, then begin(range) and end(range) are used, which can use either std::begin/std::end, or a begin(x)/end(x) pair found by argument dependent lookup.

    0 讨论(0)
  • 2021-01-13 07:59

    You're under a misconception that a ranged-for needs to know the number of iterations (the "length") in advance. It does not.

    It does need a termination condition, which has the form it != __end where __end = x.end() or __end = end(x).

    Of course, arrays can't change their size, so in this one case detecting the end and knowing the length are equivalent (length can be gotten from begin, end, and pointer subtraction).

    As Oli mentioned in the comments, the type of an array does contain length information, and std::end uses an array reference for its parameter, to avoid the decay to pointer which loses this information.

    The definition is basically:

    namespace std
    {
        template<typename T, size_t N> 
        T* end(T (&array)[N])
        {
            return array + N;
            // or if you want to be cute
            // return 1[&array];
        }
    }
    

    When the bound in an array reference is a non-type template parameter, it is deducible. Use this to your advantage when writing functions that accept arrays.

    (The compiler actually has the array + N calculation for the end built-in for arrays, it doesn't use std::end() for that. But there would be no difference after inlining anyway, and it's useful to see how you can play the same trick the compiler does.)

    0 讨论(0)
  • 2021-01-13 08:07

    The compiler knows the actual type of the array and uses a standard iterator-based loop with array (equivalent to array + 0) and array + length (equivalent to *(&array + 1)) as start respective end of the range.

    For a non-array, it first looks for begin and end in the given range, then finally uses ADL to find free functions.

    All but using ADL to find free functions will be done equally by std::begin and std::end, thus if you use using std::begin; and using std::end; in your code, unqualified name-lookup will mimic the rules of the ranged-for-loop.

    The proper section from C++1y:

    6.5.4 The range-based for statement [stmt.ranged]

    1 For a range-based for statement of the form

    for ( for-range-declaration : expression ) statement
    

    let range-init be equivalent to the expression surrounded by parentheses90 ( expression ) and for a range-based for statement of the form

    for ( for-range-declaration : braced-init-list ) statement
    

    let range-init be equivalent to the braced-init-list. In each case, a range-based for statement is equivalent to

    {
        auto && __range = range-init;
        for ( auto __begin = begin-expr, __end = 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:

    • if _RangeT is an array type, begin-expr and end-expr are __range and __range + __bound, respectively, where __bound is the array bound. If _RangeT is an array of unknown size or an array of incomplete type, the program is ill-formed;
    • if _RangeT is a class type, the unqualified-ids begin and end are looked up in the scope of class _RangeT as if by class member access lookup (3.4.5), and if either (or both) finds at least one declaration, beginexpr and end-expr are __range.begin() and __range.end(), respectively;
    • otherwise, begin-expr and end-expr are begin(__range) and end(__range), respectively, where begin and end are looked up in the associated namespaces (3.4.2). [ Note: Ordinary unqualified lookup (3.4.1) is not performed. —end note ]

    2 In the decl-specifier-seq of a for-range-declaration, each decl-specifier shall be either a type-specifier or constexpr.

    0 讨论(0)
  • 2021-01-13 08:12

    The compiler knows the number of elements of the array due to the array definition. So it uses expressions myarray and myarray + 3 to traverse the array.

    In fact the loop looks the following way

    for ( auto first = myarray, last = myarray + 3; first != last; ++first )
    {
       auto elem = *first;
       cout << elem << '\n';
    }
    

    Take into account that the range-based for statement use neither std::begin() nor std::end() for arrays as others wrote here.:)

    According to the C++ Standard

    — if _RangeT is an array type, begin-expr and end-expr are __range and __range + __bound, respectively, where __bound is the array bound. If _RangeT is an array of unknown size or an array of incomplete type, the program is ill-formed;

    0 讨论(0)
提交回复
热议问题