Search code examples
c++eigen

What's the right type for a join_rows() function?


I wrote a function that joins the rows of two 2D arrays:

template <typename S, typename T>
Array<typename S::Scalar, Dynamic, Dynamic> join_rows(const ArrayBase<S> & A, const ArrayBase<T> & B) {
    Array<typename S::Scalar, Dynamic, Dynamic> C (A.rows(), A.cols()+B.cols());
    C << A, B;
    return C;
}

I would like to write a more general function that can join more than two arrays.

It should be able to work with any iterable container, eg. std::list or std::vector, so I would use a template template paratemeter.

I can easily right the function body with two for loops, that's not the issue, I'm just struggling to figure out what the right type for such a function would be.

(ps. I'm not even sure if my above code has the best type, but it seems to do the job)


Solution

  • I'm not sure how to declare a vector of arbitrary Arrays, but you can implement a function template that combines one or more arguments directly passed to it. This is typically done by calling itself recursively, processing each successive argument:

    // end case (one argument): just forward the same array
    template <typename T>
    T&& join_rows(T&& A) {
        return std::forward<T>(A);
    }
    
    // main function template: two or more arguments
    template <typename S, typename T, typename... R>
    Array<typename S::Scalar, Dynamic, Dynamic> join_rows(const ArrayBase<S>& A,
                                                          const ArrayBase<T>& B,
                                                          const ArrayBase<R>&... rest) {
        Array<typename S::Scalar, Dynamic, Dynamic> C(A.rows(), A.cols()+B.cols());
        C << A, B;
        return join_rows(C, rest...); // call with the first two arguments combined
    }
    

    Example to illustrate usage:

    int main() {
        Array<int, 1, 3> arr1 = {1, 2, 3};
        Array<int, 1, 2> arr2 = {4, 5};
        Array<int, 1, 4> arr3 = {9, 8, 7, 6};
    
        cout << join_rows(arr1, arr2, arr3.reverse()) << endl; // 1 2 3 4 5 6 7 8 9
    
        return 0;
    }
    

    If you want to restrict the one-argument join_rows to only accept Eigen::Arrays, add an std::enable_if checking for an ArrayBase<T> base class:

    template <typename T>
    std::enable_if_t<std::is_base_of<ArrayBase<std::decay_t<T>>,std::decay_t<T>>::value, T&&>
    join_rows(T&& A) {
        return std::forward<T>(A);
    }
    

    For large Arrays, there might be more efficient ways to implement this. You could probably return a proxy object that will only allocate one new Array object.