Alex
Alex

Reputation: 876

Iterate through a struct with a pointer

I currently have this code:

struct LAYOUT {
            WORD a [8] = {::Rook, ::Knight, ::Bishop, ::Queen, ::King, ::Bishop, ::Knight, ::Rook};
            WORD b [8] = {::Pawn};
            WORD empty [32] = {::None};
            WORD c [8] = {::Pawn+0x6};
            WORD d [8] = {::Rook+0x6, ::Knight+0x6, ::Bishop+0x6, ::Queen+0x6, ::King+0x6, ::Bishop+0x6, ::Knight+0x6, ::Rook+0x6};
        }chessLayout;
LAYOUT* chessboard = &chessLayout;

The global enum fields such as ::Rook represent a word, ex: 0x2656.

My objective here is to enumerate all the elements in this structure, so I wrote this piece of code:

for(int i = 0; sizeof(LAYOUT) / 2;i++){
     printf("%x", *(reinterpret_cast<WORD*>(chessboard+i)));
}

However this returns me the first value right but then returns unrelated junk values and not the elements in the struct.

Any ideas?

Thanks in advance

Upvotes: 0

Views: 223

Answers (2)

Swift - Friday Pie
Swift - Friday Pie

Reputation: 14589

To avoid exciting tango with UBs, I'd do this in opposite way, let layout contain an array of whole data, while a,b,c,d will be getters. Even casting pointers like in code below would be edge walking, but then we can iterate through data , not worried that it might be interrupted by padding words.

using WORD = unsigned short;

namespace LayoutNS { 
      
struct Layout {
    WORD data[64];
    
    template <size_t N>
    using line = WORD [N];
    
    template <size_t N>
    line<N>* operator[] ( line<N>* (*f)(Layout&)  )
    {
        return (*f)(*this);
    }   
};

}

using LAYOUT = LayoutNS::Layout;

// better to avoid such cast either, can we go just with a pointer? Or return a copy?
LAYOUT::line<8>* line_a (LAYOUT& l) { return (LAYOUT::line<8>*)(l.data); }
LAYOUT::line<8>* line_b (LAYOUT& l) { return (LAYOUT::line<8>*)(l.data+8); }
LAYOUT::line<32>* empty (LAYOUT& l) { return (LAYOUT::line<32>*)(l.data+16); }
LAYOUT::line<8>* line_c (LAYOUT& l) { return (LAYOUT::line<8>*)(l.data+48); }
LAYOUT::line<8>* line_d (LAYOUT& l) { return (LAYOUT::line<8>*)(l.data+56); }

int main()
{
    LAYOUT test = {};
    auto test_a = test[line_a];

    std::cout << (*test_a)[1];
}

A simplified version that avoids an indirection level and C++11 features:

struct LAYOUT {
    WORD data[64];
       
    WORD* operator[] ( WORD* (*f)(LAYOUT&)  )
    {
        return (*f)(*this);
    }   
};

WORD* line_a (LAYOUT& l) { return (l.data); }
WORD* line_b (LAYOUT& l) { return (l.data+8); }
WORD* empty (LAYOUT& l) { return (l.data+16); }
WORD* line_c (LAYOUT& l) { return (l.data+48); }
WORD* line_d (LAYOUT& l) { return(l.data+56); }

int main()
{
    LAYOUT test = {{1,3,4,5}};
    auto test_a = test[line_a];

    std::cout << test_a[1];
}

Upvotes: 1

Cedric
Cedric

Reputation: 313

In the statement

LAYOUT* chessboard = &chessLayout;

/* ... */

for(int i = 0; sizeof(LAYOUT) / 2;i++){
     printf("%x", *(reinterpret_cast<WORD*>(chessboard+i)));
}

the pointer chessboard is incremented, which is of type LAYOUT*. This will move the pointer along "i * sizeof(LAYOUT)" bytes, whereas what you want is to move to the next WORD.

The expression should thus look like this (i outside the cast):

for(int i = 0; i < sizeof(LAYOUT) / 2; ++i){
     printf("%x", *(reinterpret_cast<WORD*>(chessboard)+i));
}

EDIT : As @Jarod42 pointed out, you might quickly run into UB, as we are iterating past the "end iterator" of the array a.

The exact interpretation is sometimes unclear and has cause numerous discussions on SO and in other places.

Here is a thread about this and using the C++17 offsetof functionality which should help in this context: Do we need to use std::launder when doing pointer arithmetic within a standard-layout object (e.g., with offsetof)?

I believe the detailed discussion of this is important, but has enough active threads as not to repeat it here.

Upvotes: 1

Related Questions