user1095108
user1095108

Reputation: 14603

std::copy for multidimensional arrays

I've tried the other day with gcc-4.9.1:

int main()
{
  int a[10][20][30];
  int b[10][20][30];

  ::std::copy(::std::begin(a), ::std::end(a), ::std::begin(b));

  return 0;
}

and, of course, it produced an error:

In file included from /usr/include/c++/4.9.2/bits/char_traits.h:39:0,
                 from /usr/include/c++/4.9.2/ios:40,
                 from /usr/include/c++/4.9.2/ostream:38,
                 from /usr/include/c++/4.9.2/iostream:39,
                 from t.cpp:1:
/usr/include/c++/4.9.2/bits/stl_algobase.h: In instantiation of 'static _Tp* std::__copy_move<_IsMove, true, std::random_access_iterator_tag>::__copy_m(const _Tp*, const _Tp*, _Tp*) [with _Tp = int [20][30]; bool _IsMove = false]':
/usr/include/c++/4.9.2/bits/stl_algobase.h:396:70:   required from '_OI std::__copy_move_a(_II, _II, _OI) [with bool _IsMove = false; _II = int (*)[20][30]; _OI = int (*)[20][30]]'
/usr/include/c++/4.9.2/bits/stl_algobase.h:434:38:   required from '_OI std::__copy_move_a2(_II, _II, _OI) [with bool _IsMove = false; _II = int (*)[20][30]; _OI = int (*)[20][30]]'
/usr/include/c++/4.9.2/bits/stl_algobase.h:466:17:   required from '_OI std::copy(_II, _II, _OI) [with _II = int (*)[20][30]; _OI = int (*)[20][30]]'
t.cpp:10:62:   required from here
/usr/include/c++/4.9.2/bits/stl_algobase.h:373:4: error: static assertion failed: type is not assignable
    static_assert( is_copy_assignable<_Tp>::value,
    ^

I think this is not so much a problem with std::copy(), as with std::begin() and std::end() that would handle multidimensional arrays. Is this an omission with the current C++ standard and how to work around it?

EDIT: I am convinced, the standard could fix this problem:

namespace std
{

template <typename T, size_t M, size_t N>
constexpr typename remove_all_extents<T>::type*
begin(T (&array)[M][N])
{
  return begin(array[0]);
}

template <typename T, size_t M, size_t N>
constexpr typename remove_all_extents<T>::type*
end(T (&array)[M][N])
{
  return end(array[M - 1]);
}

}

Upvotes: 4

Views: 3673

Answers (6)

user6037065
user6037065

Reputation: 11

How about using a union with a flattened version.

int main()
{
  union
  {
    int a[10][20][30];
    int fa[10 * 20 * 30];
  };

  union
  {
    int b[10][20][30];
    int fb[10 * 20 * 30];
  };

  ::std::copy(::std::begin(fa), ::std::end(fa), ::std::begin(fb));

  return 0;
}

Upvotes: 1

user1095108
user1095108

Reputation: 14603

After reading through all the answers, here's my 5 cents, that seem to work:

template <typename T>
constexpr T* begin(T& value) noexcept
{
  return &value;
}

template <typename T, ::std::size_t N>
constexpr typename ::std::remove_all_extents<T>::type*
begin(T (&array)[N]) noexcept
{
  return begin(array[0]);
}

template <typename T>
constexpr T* end(T& value) noexcept
{
  return &value + 1;
}

template <typename T, ::std::size_t N>
constexpr typename ::std::remove_all_extents<T>::type*
end(T (&array)[N]) noexcept
{
  return end(array[N - 1]);
}

I won't accept my own answer and, if something is wrong, downvote or comment and I'll remove.

Upvotes: 2

Christophe
Christophe

Reputation: 73366

Here another template variant, which works for any multimensional array, whatever the number of dimensions. It uses the fact that elements of a multidimensional are contiguous and casting to process the array as if it was a flat one dimensional one:

template <typename T>
typename std::remove_all_extents<T>::type* mbegin(T& arr) {
    return reinterpret_cast<typename std::remove_all_extents<T>::type*>(&arr);
}
template <typename T>
size_t msize(const T& a) 
{
    return sizeof(T) / sizeof(typename std::remove_all_extents<T>::type);
}
template <typename T>
typename std::remove_all_extents<T>::type* mend(T& arr) {
    return reinterpret_cast<typename std::remove_all_extents<T>::type*>(&arr)+msize(arr);
}

You call it:

::std::copy(mbegin(a), mend(a), mbegin(b));

The trick here is to use the type T for the multidimensional array (int[][]..[]) and use typename std::remove_all_extents<T>::type to remove the dimensions and get the base type (int).

Upvotes: 3

Christophe
Christophe

Reputation: 73366

This answer has already found some very interesting answers. Most are based on the fact that the C++ standard ensures that elements of a multidimensional array are contiguous, making it possible (via casting) to process the array as if it was a one dimensional one.

I propose this variant for the sake of completeness. It uses templates and automatic deduction of array size:

template <typename T, size_t N1, size_t N2, size_t N3>
T* begin(T(&arr)[N1][N2][N3]) {
    return reinterpret_cast<T*>(arr);  
}
template <typename T, size_t N1, size_t N2, size_t N3>
T* end (T(&arr)[N1][N2][N3]) {
    return reinterpret_cast<T*>(arr)+N1*N2*N3; 

You can then proceed with the copy:

::std::copy(begin(a), end(a), begin(b));

Another approach could be to define a 3D iterator using the same template approach.

Upvotes: 2

Columbo
Columbo

Reputation: 60979

It won't work with a simple copy call as plain arrays cannot be copied or assigned. However, you can copy the underlying elements and treat the multidimensional array as a one-dimensional one.

One convenient possibility is to use a flattened range accessors:

// For convenient trailing-return-types in C++11:
#define AUTO_RETURN(...) \
 noexcept(noexcept(__VA_ARGS__)) -> decltype(__VA_ARGS__) {return (__VA_ARGS__);}

template <typename T>
constexpr auto decayed_begin(T&& c)
AUTO_RETURN(std::begin(std::forward<T>(c)))

template <typename T>
constexpr auto decayed_end(T&& c)
AUTO_RETURN(std::end(std::forward<T>(c)))

template <typename T, std::size_t N>
constexpr auto decayed_begin(T(&c)[N])
AUTO_RETURN(reinterpret_cast<typename std::remove_all_extents<T>::type*>(c    ))

template <typename T, std::size_t N>
constexpr auto decayed_end(T(&c)[N])
AUTO_RETURN(reinterpret_cast<typename std::remove_all_extents<T>::type*>(c + N))

Then,

std::copy( decayed_begin(a), decayed_end(a), decayed_begin(b) );

Demo.

Upvotes: 4

Vlad from Moscow
Vlad from Moscow

Reputation: 310930

Arrays have no assignment operator. So you need to copy the whole array element by element of type int.

For example you can write

std::copy( reinterpret_cast<int *>( a ),
           reinterpret_cast<int *>( a ) + 10 * 20 * 30,
           reinterpret_cast<int *>( b ) );

Or you can use std::for_each or std::transform with some compound lambda expressions.

Upvotes: 4

Related Questions