3

I have some shared memory populated by specialized hardware. It's declared as an array of structs, like:

struct port {
    int data[10];
    char port_id[8];
}

struct bus {
    port ports[5];
    char bus_id[8];
}

struct bus busses[10];

I'm (re)learning C++, and wanted to use C++11's ranged for loops to iterate over the data.

HOWEVER: That last dimension of the array (data[10]), I only care about the first 4 elements. Is there a way to take a slice of the data and use it in the for() statement?

Like

for (auto & b : busses) {
    for (auto & p : bus.ports) {
        for (auto & d : port.data[0 through 3]) {
             store_the_address_of_d_for_use_elsewhere(d);
        }
     }
 }

Is there a way to use a cast in the innermost for loop, so that it appears like there's only 4 elements? The address of the data is important because I'm going to refer directly to it later using pointers.

3 Answers3

4

This is probably one of those times when a good old-fashioned for (int i = 0; i < 4; i++) is your best bet.

Don't overthink it, and don't try to use "new" features just for the sake of it, creating more complexity and more work in the process.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 1
    Ultimately, I agree with you, and I went with using a regular for loop. If the solution was a simple one, it would have pleased my Python-damaged brain to use the new `for` syntax. :-) – A Humble Supplicant Nov 06 '16 at 04:40
3
template<class T>
struct array_view {
  T* b = 0;
  T* e = 0;
  T* begin() const { return b; }
  T* end() const { return e; }
  std::size_t size() const { return end()-begin(); }
  T& front() const { return *begin(); }
  T& back() const { return *(end()-1); }

  // basic constructors:
  array_view(T* s, T* f):b(s), e(f) {}
  array_view(T* s, std::size_t N):array_view(s, s+N) {}

  // default ctors: (no need for move)
  array_view()=default;
  array_view(array_view const&)=default;
  array_view& operator=(array_view const&)=default;

  // advanced constructors:
  template<class U>
  using is_compatible = std::integral_constant<bool,
    std::is_same<U, T*>{} || std::is_same<U, T const*>{} ||
    std::is_same<U, T volatile*>{} || std::is_same<U, T volatile const*>{}
  >;
  // this one consumes containers with a compatible .data():
  template<class C,
    typename std::enable_if<is_compatible< decltype(std::declval<C&>().data()) >{}, int>::type = 0
  >
  array_view( C&& c ): array_view( c.data(), c.size() ) {}
  // this one consumes compatible arrays:
  template<class U, std::size_t N,
    typename std::enable_if<is_compatible< U* >{}, int>::type = 0
  >
  array_view( U(&arr)[N] ):
    array_view( arr, N )
  {}

  // create a modified view:
  array_view without_front( std::size_t N = 1 ) const {
    return {begin()+(std::min)(size(), N), end()};
  }
  array_view without_back( std::size_t N = 1 ) const {
    return {begin(), end()-(std::min)(size(), N)};
  }
  array_view only_front( std::size_t N = 1 ) const {
    return {begin(), begin()+(std::min)(size(), N)};
  }
  array_view only_back( std::size_t N = 1 ) const {
    return {end()-(std::min)(size(), N), end()};
  }
};

Now some functions that let you easily create it:

template<class T, std::size_t N>
array_view<T> array_view_of( T(&arr)[N] ) {
  return arr;
}
template<class C,
  class Data = decltype( std::declval<C&>().data() ),
  class T = typename std::remove_pointer<Data>::type
>
array_view<T> array_view_of( C&& c ) {
  return std::forward<C>(c);
}
template<class T>
array_view<T> array_view_of( T* s, std::size_t N ) {
  return {s, N};
}
template<class T>
array_view<T> array_view_of( T* s, T* e ) {
  return {s, e};
}

and we are done the boilerplate part.

for (auto & b : bus) {
  for (auto & p : bus.port) {
    for (auto & d : array_view_of(bus.data).only_front(4)) {
      store_the_address_of_d_for_use_elsewhere(d);
    }
  }
}

live example

Now I would only advocate this approach because array_view is shockingly useful in many different applications. Writing it just for this case is silly.

Note that the above array_view is a multiply-iterated class; I've written it here before. This one is, in my opinion, better than the previous ones, other than the annoying -isms I had to use.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • That's pretty amazing; I don't think I'm familiar enough yet with C++ (especially C++11) to entirely appreciate everything going on there. Your code has inspired me to learn more about the templating system, which is pretty new to me. – A Humble Supplicant Nov 06 '16 at 04:42
  • `(std::min)(size(), N)` Is this an attempt to improve diagnostics when a macro `min` is defined, or something else? I'm referring to the encapsulation of the function name within parenthesis. – Lightness Races in Orbit Nov 06 '16 at 14:40
  • @LightnessRacesinOrbit On MSVC, `(std::min)(blah, blah)` will work despite the fact that `#define min(a,b) blah` is defined, because the `()` convince MSVC that this isn't a 2-argument macro call. Elsewhere it is harmless. So it is ingrained into my coding fingers. No idea if that is standard preprocessor compliant or not. – Yakk - Adam Nevraumont Nov 06 '16 at 15:17
  • I don't know whether to laugh or cry. – Lightness Races in Orbit Nov 06 '16 at 15:18
2
for (auto & d : reinterpret_cast<int (&)[4]>(p))
pat
  • 763
  • 4
  • 12