tags:

views:

881

answers:

10

I was under the assumption that STL functions could be used only with STL data containers (like vector) until I saw this piece of code:

#include <functional>
#include <iostream>
#include <numeric>
using namespace std;

int main()
{
    int a[] = {9, 8, 7};
    cerr << "Sum: " << accumulate(&a[0], &a[3], 0, plus<int>()) << endl;
    return 0;
}

It compiles and runs without any warnings or errors with g++, giving the correct output sum of 24.

Is such usage of arrays with STL functions allowed by the C++/STL standard? If yes, how do archaic structures like arrays fit into the grand STL plan of templated iterators, containers and functions? Also, are there any caveats or details in such usage that the programmer should be careful about?

A: 

As int a[] can be treated as a pointer. And in C++ pointers can be incremented and point after that to the next element. And as pointers can be compared then pointers can be used as iterators.

There are requirements for iterators pointed in the standard 24.1 section. And pointers meet them. Here is some of them

All iterators i support the expression*i

Just as a regular pointer to an array guarantees that there is a pointer value pointing past the last element of the array, so for any iterator type there is an iterator value that points past the last element of a corresponding container.

Mykola Golubyev
No, int a[10] is not a pointer. It can decay into a pointer but it is a different type altogether and the compiler has more info with the array than with the pointer (size: note, this is the compiler, not an attribute, and only if it has not already decayed into a pointer)
David Rodríguez - dribeas
+17  A: 

The standard has designed iterators to feel and behave as much like pointers as possible. Also, since iterators are based on templates, the only relevant thing is that the iterator type has the proper operators defined. The result is that pointers will out-of-the-box behave just like random access iterators.

In fact, a possible implementation of std::vector<T>::iterator is to just make it a T*.

Of course, for an array you won't have the useful begin() and end() methods to find the valid iterator range, but that's the problem you always have with C style arrays.

Edit: Actually, as has been mentioned in the comments and other answers, you can implement those functions for arrays if the array is not dynamic and has not decayed into a pointer. But my basic point was that you have to be more careful than when using the standard containers.

CAdaker
Wouldn't end() just be start() + size(), with a pointer implementation? I'm not sure, but mostly you see code like "while(iter != vector.end())", that would work ...
unwind
unwind: Yes, that would work, and is what people usually do.
jalf
You can provide in a simple template library auxiliary functions that provide begin/end equivalents for arrays. That is, as long as the array has not decayed into a pointer through a function/method call, and the array is not dynamic
David Rodríguez - dribeas
@unwind: where size() is not sizeof(array).
David Rodríguez - dribeas
Yes, end() == begin() + size(). What I meant was that for an array you need to keep track of the size manually. Or use sizeof-tricks, but (as people have said) that doesn't always work.
CAdaker
You can write a template helper function which returns the size of an array, and gives a compiler error if used on a pointer though. That helps a bit in avoiding sizeof errors. A comment for one of the other answers has an example.
jalf
boost::range also has such begin/end functions for arrays
Johannes Schaub - litb
I think an important thing to understand about the STL is that it implements a bunch of "Concepts". It models interfaces with templates that require their arguments to comply with a certain Concept. The SQL page has a good reference http://www.sgi.com/tech/stl/table_of_contents.html
MGoDave
+1  A: 

Yes and this is on purpose. Iterators can be implemented as pointers and therefore you can use pointers as iterators.

Edouard A.
+3  A: 

Is such usage of arrays with STL functions allowed by the standard?

yes

If yes, how do archaic structures like arrays fit into the grand STL plan of templated iterators, containers and functions?

Iterators was designed with similar semantic as pointers.

Also, are there any caveats or details in such usage that the programmer should be careful about?

I prefer next usage:

int a[] = {9, 8, 7};
const size_t a_size = lengthof( a );
cerr << "Sum: " << accumulate( a, a + a_size , 0, plus<int>()) << endl;

Or it much better and safer to use boost::array:

boost::array< int, 3 > a = { 9, 8, 7 };
cerr << "Sum: " << accumulate( a.begin(), a.end(), 0, plus<int>()) << endl;
bb
what is lengthof?
Mykola Golubyev
your own function=)
bb
I am not sure it is possible to write your own function that will return length of the C array.
Mykola Golubyev
a typical implementation of lengthof would be: #define lengthof(x) sizeof(x)/sizeof(x[0])You could probably also do something like: template <class T, int N> int lengthof(const T[N] _) { return N; }But I'n not sure if that would work on all C++ compilers.
Niki
template < typename T, size_t N >size_t lengthof( T(}
bb
I Googled for lengthof, wondering if I had missed a C++09 innovation.
Thomas L Holaday
+2  A: 

Just a comment on Mykola's answer:

Arrays are not pointers, even if they tend to decay into pointers really easily. The compiler has more info on an array than on a container:

namespace array {
   template <typename T, int N>
   size_t size( T (&a)[N] ) {
      return N;
   }
   template <typename T, int N>
   T* begin( T (&a)[N] ) {
      return &a[0];
   }
   template <typename T, int N>
   T* end( T (&a)[N] ) {
      return &a[N];
   }
}
int main()
{
   int theArray[] = { 1, 2, 3, 4 };
   std::cout << array::size( theArray ) << std::endl; // will print 4
   std::cout 
      << std::accumulate( array::begin( theArray ), array::end( theArray ), 0, std::plus<int>() )
      << std::endl; // will print 10
}

While you cannot ask about the size of the array, the compiler will resolve it when calling the given templates.

Now, if you call a function that takes a int a[] (note that there is no size), that is similar to defining an int* parameter, and the size info is lost in the way. The compiler will not be able to determine the size of the array inside the function: the array has decayed into a pointer.

If , on the other hand, you define the parameter as int a[10] then the information is lost, but you will not be able to call the function with an array of a different size. This is completely different than the C version, at least prior to C99 have not checked lately[*]. In C the compiler will ignore the number in the argument and the signature will be equivalent to the previous version.

@litb: You are right. I had this test around, but it is with a reference to an array, not with an array. Thanks for pointing it out.

dribeas@golden:array_size$ cat test.cpp 
void f( int (&x)[10] ) {}
int main()
{
    int array[20];
    f( array ); // invalid initialization of reference of type 'int (&)[10]' from...
}
David Rodríguez - dribeas
"If, on the other hand, you define the parameter as int a[10] then the information is lost, but you will not be able to call the function with an array of a different size." C++ will completely ignore the 10 either :)
Johannes Schaub - litb
You must add "int N" as a template parameter for your functions size, begin and end...
Benoît
@Benoît: yes, I forgot. Corrected. Thanks :)
David Rodríguez - dribeas
Ashwin
Ash, already done. yay! http://stackoverflow.com/questions/437150/can-someone-explain-this-template-code-that-gives-me-the-size-of-an-array/437178#437178 have fun!
Johannes Schaub - litb
I had no idea what keywords to use to search for that. Thanks a ton! :-)
Ashwin
+2  A: 

Instead of using arrays and then worrying about passing them to STL functions (what one might call 'forwards compatibility', and is therefore fragile), IMO you should use std::vector and use its (stable and dependable) backwards compatibility with functions that take arrays, should you ever need to use them.

So your code becomes:

#include <functional>
#include <iostream>
#include <numeric>
#include <vector>
using namespace std;

int main()
{
    vector<int> a(3);
    a[0] = 9;
    a[1] = 8;
    a[2] = 7;
    cerr << "Sum: " << accumulate(a.begin(), a.end(), 0, plus<int>()) << endl;
    return 0;
}

And if you ever need to pass 'a' to a C API you can do so, thanks to vectors binary compatibility with arrays.

Visage
I am aware of the use of vectors, it is just that I didn't know that arrays could be used with STL.Could you explain how to achieve the vector to array conversion you mention at the end?
Ashwin
Well, lets say you have a c function: void foo(const int *pInts, int size) where size is the number of elements in pInts.You can call that with a vecotr like so:vector<int> v;..populate v with datafoo(
Visage
+20  A: 

Well, you ask about an array. You can just easily get a pointer to its elements, so it basically boils down to the question whether pointers can be used transparently with STL functions. A pointer actually is the most powerful kind of an iterator. There are different kinds

  • Input iterator: Only forward and one-pass, and only read
  • Output iterator: Only forward and one-pass, and only write


  • Forward iterator: Only forward, and read/write
  • Bidirectional iterator: Forward and backward, and read/write
  • Random access iterator: Arbitrary steps forward and backward in one breath, and read/write

Now each iterator in the second group supports all the things of all iterators mentioned before it. A pointer models the last kind of iterators - a random access iterator. You may add/subtract an arbitrary integer and you may read and write. And all except the output iterator has a operator-> that can be used to access a member of the element type we iterate over.

Normally, iterators have several typedefs as members

  • value_type - what the iterator iterates over (int, bool, string, ...)
  • reference - reference to the value_type
  • pointer - pointer to the value_type
  • difference_type - what type the distance between two iterators has (returned by std::distance).
  • iterator_category - this is a tag-type: it is typedefed to a type that represents the kind of the iterator. either std::input_iterator_tag, ..., std::random_access_iterator_tag. Algorithms can use it to overload on different kinds of iterators (like std::distance is faster for random access iterators, because it can just return a - b)

Now, a pointer of course does not have those members. C++ has an iterator_traits template and specializes it for pointers. So if you want to get the value type of any iterator, you do

iterator_traits<T>::value_type

And whether it is a pointer or some other iterator, it will give you the value_type of that iterator.

So - yes, a pointer can very well be used with STL algorithms. As someone else mentioned, even std::vector<T>::iterator can be a T*. A pointer is a very good example of an iterator even. Because it is so exceedingly simple but at the same time so powerful that it can iterate over a range.

Johannes Schaub - litb
That's a comprehensive reply, thanks for the effort! :-)
Ashwin
+1  A: 

The introduction to boost::array (a simple templated wrapper for conventional arrays, which also defines STL-compatible iterator types and begin()/end() etc) contains some interesting discussion of their degree of compatability with STL.

timday
That was an informative read, thanks! :-)
Ashwin
+6  A: 

Short answer: STL algorithms are generally defined to work with iterators of various sorts. An iterator is defined by its behavior: it must be dereferenceable with *, it must be incrementable with ++, and various other things that also define what sort of iterator it is (the most general is random access). Remember that STL algorithms are templates, so the question is one of syntax. Similarly, a class instance with operator() defined works syntactically just like a function, so they can be used interchangeably.

A pointer does everything needed to be a random-access iterator. Therefore, it is a random-access iterator, and can be used as such in STL algorithms. You could look at vector implementations; you're very likely to find that vector<whatever>::iterator is a whatever *.

This doesn't make an array a valid STL container, but it does make pointers valid STL iterators.

David Thornley
I liked your concluding sentence :-)
Ashwin
A: 

Pointers model Trivial Iterator, and pointer from arrays model Random Access Iterator. So yes, it's perfectly legal.

If you are interested on the usage constraints of each S(T)L algorithm, familiarize yourself the iterator models.

Luc Hermitte