Ricky Spanish
Ricky Spanish

Reputation: 705

Getting an std::array reference without using templates

If a function receives an array range (begin/end) as pointers, is there a way to treat that as std::array again?

void xyz(int* begin, int* end) {
  // at this point i know that at this memory range there's
  // an array<int> of end - begin size
  // is there any way i can get that back?
}

int main() {
  array<int, 5> x = {1, 2, 3, 4, 5};

  xyz(x.begin(), x.end());
}

I know that i can pass x as parameter but i'd have to specify it's size. The way around that is to use templates, but i wonder is there a some way to reconstruct a typed reference to x without using templates and without copying it?

If (begin/end) isn't enough to reconstruct the type inside xyz is there any other way to give xyz memory representation of an array, and let it reconstruct the type... trusting that it's there at this memory address?

Upvotes: 0

Views: 184

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 596602

std::array is implemented as a struct holding a C-style fixed-length array as its sole data member. The begin() iterator is usually a raw pointer to the 1st element of the array 1.

1: @bitmask makes a good point in comments: "Note that std::array<T,N>::iterator does not have to be a raw-pointer. It usually is, but doesn't HAVE to be. Nor does it have to be convertible to a T*. So your code is invalid C++ and compiles because of an implementation detail of your compiler.*"

The address of the 1st element of an array is guaranteed to be the same address as the array itself. And the address of the 1st member of a struct is guaranteed to be the same address as the struct itself. So, in theory, provided that the begin iterator is really a raw pointer to the 1st array element, then you should be able to type-cast begin to a std::array* pointer - but only if you know at compile-time the EXACT template parameter values that were used to instantiate that std::array, eg:

void xyz(int* begin, int* end) {
  using array_type = array<int, 5>; // OK
  array_type *arr = reinterpret_cast<array_type*>(begin);
  ...
}

int main() {
  array<int, 5> x = {1, 2, 3, 4, 5};

  xyz(x.begin(), x.end());
}

However, this will fail miserably if you can't guarantee that begin comes only from a std::array, and is a raw pointer to its 1st element.

And while you can determine the necessary T template parameter from the type of the iterators begin passed in, you cannot determine the necessary N template parameter using just iterators. That value must be a compile-time constant, but getting the distance between the 2 iterators can only be determined at runtime instead:

void xyz(int* begin, int* end) {
  using array_type = array<int, ???>; // <-- can't use (end-begin) here!!!
  array_type *arr = reinterpret_cast<array_type*>(begin);
  ...
}

Or:

template<typename Iter>
void xyz(Iter begin, Iter end) {
  using array_type = array<iterator_traits<Iter>::value_type, ???>; // <-- can't use (end-begin) here!!!
  array_type *arr = reinterpret_cast<array_type*>(begin);
  ...
}

The whole point of using iterators as function parameters in the first place is to abstract away the container type so it is not known or needed. So, it is very difficult, if not impossible, to determine the original container type from information gleamed from its iterators.

So, in this situation, if you really need access to the std::array then you are best off simply passing the whole std::array itself, not its iterators:

template<typename T, size_t N>
void xyz(array<T, N> &arr) {
  ...
}

int main() {
  array<int, 5> x = {1, 2, 3, 4, 5};

  xyz(x);
}

Upvotes: 3

Related Questions