Search code examples
c++multidimensional-arraystlstl-algorithm

using std::transform on a 2d C array into a 1d C array


I am trying to use std::transform to convert a simple 2d array (of Foo structs) into a 1d (transformed) array of Bar structs.

// convert 2d array of Foo structs to 1d array of Bar structs
// suitable for constructing a Baz struct.
Foo array2d[3][2] = 
{ 
    { 
        {1,2}, {3,4} 
    }, 
    { 
        {5,6}, {7,8} 
    }, 
    { 
        {9,10}, {11,12} 
    } 
};

The conversion in this simple example just reverses the field order as both structs are really the same type. In my application these are completely different types.

using Foo = struct {
    uint32_t a;
    uint32_t b;
};

using Bar = struct {
    uint32_t c;
    uint32_t d;
};

The idea is that this 1d array of Bar structs could be used to construct a Baz struct.

I am having trouble with the lambda converter(s). I believe that the outer one takes a row at at time while the inner one takes a column at a time where the actual Foo->Bar conversion takes place. In the live demo, I had to comment out the std::transform taking the 2d array and replace it with a flattened version where I cast the 2d array to a 1d array (of size row times col). This worked flawlessly - but I was trying to stick to the parameter types without having to resort to the reinterpret_cast<>.

    std::vector<Bar> array1d;
    array1d.reserve(Baz::gArraySize);
#if 0
    // I don't know how to use the transform on the 2d array
    std::transform(std::cbegin(array2d), std::cend(array2d),
        std::back_inserter(array1d),
        [](const Foo(&rRow)[Baz::gNumCols]) {
        std::transform(std::cbegin(rRow), std::cend(rRow),
            [](const Foo& rNext) -> Bar {
                // reverse the order of the fields
                return Bar{ rNext.b, rNext.a };
            });
        });
#else
    // Only workaround is to cast the 2d array to a 1d array using reinterpret cast<>
    const auto& special = reinterpret_cast<const Foo(&)[Baz::gArraySize]>(array2d);
    // I don't know how to use the transform on the 2d array
    std::transform(std::cbegin(special), std::cend(special),
        std::back_inserter(array1d),
        [](const Foo& rNext) -> Bar {
            // reverse the order of the fields
            return Bar{ rNext.b, rNext.a };
        });
#endif
    // construct from transformed 2d array
    Baz myBaz(reinterpret_cast<const Bar(&)[Baz::gArraySize]>(array1d[0]));
    std::cout << myBaz;

producing the expected output as follows:

g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
Bar{c=2, d=1},
Bar{c=4, d=3},
Bar{c=6, d=5},
Bar{c=8, d=7},
Bar{c=10, d=9},
Bar{c=12, d=11},

The structs are in C like array form as these come from an external source. I'm not sure if what I am trying to do is possible with std::transform, but I would like to use STL algorithms rather than unravel the loops by hand.

I created the following live coliru demo to show what I was trying to achieve - but its got many errors with the transform in place. Note that the array being passed to Baz depends on the fact that the std::vector allocates data structures contiguously in memory (this is guaranteed by the STL).

struct Baz {
    constexpr static int gNumRows = 3;
    constexpr static int gNumCols = 2;
    constexpr static int gArraySize = gNumRows * gNumCols;
    Bar arrayField[gArraySize];
    // explicit constructor from C style fixed size array.
    explicit Baz(const Bar(&rParam)[gArraySize])
        : arrayField{}
    {
        std::memcpy(arrayField, rParam, gArraySize * sizeof(Bar));
    }

    friend std::ostream& operator<<(
        std::ostream& os, const Baz& rhs) {
        for (auto next : rhs.arrayField) {
            os << "Bar{c=" << next.c << ", d=" << next.d << "},\n";
        }
        return os;
    }

};

Solution

  • The lambda you pass to the outer transform doesn't return anything and it really can't because it supposed to return one value for each element of the input range (your two-dimensional array). But each element of that array has two values so every iteration of the transform would produce two values when it should produce one, that's why you can't use transform here.

    Given this, it would be a lot easier and a lot more readable to use simple loops here:

    for (auto &&row : array2d)
        for (auto &&foo : row)
            oneDimArray.push_back(Bar{ foo.b, foo.a });
    

    and leave STL algorithms for the cases when they actually make your life easier :).