简体   繁体   中英

C++ - Using array/pointers in vector (or other container) constructor that expects type Iterator. How is that possible?

Below text is from a C++ online course. It says that the constructor of the vector class

 template <class InputIterator>
          vector ( InputIterator first, InputIterator last, const Allocator& = Allocator() );

can receive pointers as first ( InputIterator first ) and second parameter ( InputIterator last ).

The next constructor uses iterators to initialize itself. It will create a vector with a copy of values from first (inclusive) to last (exclusive). In the most typical case, this constructor creates a new vector using elements from an already existing collection. But due to the fact that iterators are defined as a set of operations instead of a certain type, it is also possible to use normal pointers. This remark leads to a conclusion that you can use a normal C++ array to initialize a collection. And in fact you can.

#include <vector>
#include <iostream>

using namespace std;

int main()
{
    int a1[]={1,2,3,4,5,6,7,8,9,10};
    //first one
    vector<int> v1(a1, a1+10);
    cout<<"Size (v1):  "<<v1.size()<<endl;
    for(unsigned i = 0; i < v1.size(); ++i)
    {
        cout<< v1[i]<<" ";
    }
    cout<<endl;
    //second one;
    vector<int> v2(a1+5,a1+10);
    cout<<"Size (v2):  "<<v2.size()<<endl;
    for(unsigned i = 0; i < v2.size(); ++i)
    {
        cout<< v2[i]<<" ";
    }
    cout<<endl;
    return 0;
}

I can get used to this, but I don't really understand why it is possible. I'd like to understand this technique.

My question in this case (see code) is how is it possible that I can simply put an array address instead of an iterator?

In the above code an element of type int * is put as a parameter of type InputIterator . This confuses me.

All the story is about what the constructs expects from an iterator. It expects to be able to increment it (using ++), to deference it (using *). An interator is simply either a class that overloads the ++ and the * operator, or a basic pointer type that naturally supports both operations.

C++ iterators are actually carefully designed to generalize the semantics of pointers and mimic their syntax (hence the operator* overload).

See the general discussion in the original SGI docs or the treatment of more recent C++ versions here , and note in particular that:

  1. pointers satisfy all the requirements of RandomAccessIterators
  2. RandomAccessIterators model a superset of the requirements of an InputIterator
  3. the std::vector constructor only requires InputIterator

If you don't really believe #1, you may also be interested to see that the standard library explicitly provides iterator traits for raw pointers , and if you're still struggling after that, take a look at the implementation of the constructor, and reassure yourself that all operations on its arguments are well-formed for pointers.

To answer your last question a pointer points to a place in memory. Since memory is random access pointers can be classified as a having random access. Iterators have the same type of classifications. cplusplus.com has a nice layout showing which type of iterator support what here . With that we can see that input iterators are the lowest type and they can do the least amount of operations. Since a pointer is the same as a random access iterator and a random access iterator can do everything an input iterator can do there isn't any issue with that.

If this was the other way around and you had a function that wanted a random access iterator and you supplied a forward iterator there is no guarantee that the code would work. This is because the function could be relying on some operation a random access iterator can do that the forward iterator can't do.

Generally when you write this type of code and you specify what iterator type you want you specify the minimal type that allows your code to compile. Something that just prints the contents just needs a forward iterator as you are just traversing the contents where sorting needs a random access.

Because an array stores it's elements in contiguous memory. So if you know the size of the type, and the pointer to the first element, you can figure out the address of all the remaining elements.

int values[10];
&values[5] == &values[0] + 5 // due to pointer arithmetic

§5.7 Additive operators

The additive operators + and - group left-to-right. The usual arithmetic conversions are performed for operands of arithmetic or enumeration type.

For addition , either both operands shall have arithmetic or unscoped enumeration type, or one operand shall be a pointer to a completely-defined object type and the other shall have integral or unscoped enumeration type .

For subtraction, one of the following shall hold:
— both operands have arithmetic or unscoped enumeration type; or
— both operands are pointers to cv-qualified or cv-unqualified versions of the same completely-definedobject type; or
— the left operand is a pointer to a completely-defined object type and the right operand has integral or unscoped enumeration type.

...

When an expression that has integral type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integral expression . In other words, if the expression P points to the i-th element of an array object, the expressions (P)+N (equivalently, N+(P)) and (P)-N (where N has the value n) point to, respectively, the i + n-th and i − n-th elements of the array object, provided they exist. Moreover, if the expression P points to the last element of an array object, the expression (P)+1 points one past the last element of the array object, and if the expression Q points one past the last element of an array object, the expression (Q)-1 points to the last element of the array object. If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.

So basically, the standard defines the arithmetic between a pointer T* and an integral type

T* t + n  // where n is an offset (of integral type)

To result in another T* that is n * sizeof(T) from the initial t .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM