0

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?

Ricky Spanish
  • 705
  • 6
  • 16
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/213902/discussion-on-question-by-ricky-spanish-getting-an-stdarray-reference-without). – Samuel Liew May 15 '20 at 06:25

1 Answers1

3

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);
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Really appreciate the in depth explanation, very helpful. but question is about another way other than templates – Ricky Spanish May 15 '20 at 00:30
  • 3
    This looks UB. Like ... extremely! – bitmask May 15 '20 at 00:30
  • 2
    @bitmask yes, very – Remy Lebeau May 15 '20 at 00:31
  • @bitmask can you, please, elaborate? – Ricky Spanish May 15 '20 at 00:34
  • @RickySpanish There are a lot of IFs that have to be TRUE for this to even be possible to work. I've tried to explain them in my answer for you. – Remy Lebeau May 15 '20 at 00:35
  • @RickySpanish Elaborate on what? – bitmask May 15 '20 at 00:37
  • @bitmask sorry i didn't see your explanation that begin doesn't have to be a pointer – Ricky Spanish May 15 '20 at 00:38
  • 1
    @RickySpanish The whole answer boils down to: **No**, there is no legal way to convert an iterator to a container into the container itself. – NathanOliver May 15 '20 at 00:39
  • @NathanOliver safest illegal way to treat a block of memory as specific type essentially using it's functionality. The problem extends out of arrays i think. it's like i have this chunk of memory that i know for fact it's of type X, and i want to treat it like so – Ricky Spanish May 15 '20 at 00:44
  • 1
    @RickySpanish There is no safe illegal. UB means you can't reason about the code anymore and that means it's not safe. – NathanOliver May 15 '20 at 00:47
  • 1
    @RickySpanish "*i have this chunk of memory that i know for fact it's of type X, and i want to treat it like so*" - That is what [`reinterpret_cast`](https://en.cppreference.com/w/cpp/language/reinterpret_cast) exists for: "*[reinterpret_cast] is purely a compile-time directive which instructs the compiler to treat `expression` as if it had the type `new_type`.*". But there are a lot of restrictions on how `reinterpret_cast` can be used safely and *legally*. This situation falls under "type punning" and really violates the [strict alias rule](https://stackoverflow.com/questions/98650/). – Remy Lebeau May 15 '20 at 00:47
  • @RemyLebeau but i can't reinterpret cast an `array` this is what's bugging my little brain. But again it's because of the size needing to be compile time known, so i thought what if there's a way to dynamic cast at runtime... the type is there and the size is known – Ricky Spanish May 15 '20 at 00:50
  • @RickySpanish you can `reinterpret_cast` a *pointer* of one type into a *pointer* of another type, which is exactly what I show in my answer. `dynamic_cast` won't work, because `std::array` is not a polymorphic type (no virtual methods), plus you still have to specify the *exact* type to cast to, which goes back to the issue of not knowing the array's `N` parameter. Same with `static_cast`, too. Without knowing the *exact* `std::array` template parameters, there is simply no *casting* that you can perform. That doesn't mean you can't access the array's *data*, you can (via the iterators). – Remy Lebeau May 15 '20 at 00:53
  • alright, so there's no way on earth that i can use `end-begin` to specify the size at runtime – Ricky Spanish May 15 '20 at 00:59
  • @RickySpanish *at runtime*, you can certainly calculate the size, eg: `size_t size = end - begin;` or `auto size = std::distance(begin, end);` You just can't use that value IN TEMPLATE PARAMETERS, eg: `size_t size = ...; std::array *arr = ...;`, as template parameter MUST be known *at compile-time* only. – Remy Lebeau May 15 '20 at 01:06