0

i am trying to change the data type of all the elements into a nested C array, something like this.

const int a[2][3] = {
   {1,2,3},
   {4,5,6}
}

The arrays are "multidimensional", and i don't know how many dimension they have.
I figured out something like this:

template <class D, class T, unsigned S>
inline D& uniform(T (&t)[S]) {
   D v[S];
   for (int k = 0; k < S; k++) {
      v[k] = D(t[k]);
   }
   return v;
}
auto b = uniform<float>( a );

However the previous code works (or at least it is supposed to work) only if a is 1D, is there a way to make it work over multidimensional C arrays?

Lundin
  • 195,001
  • 40
  • 254
  • 396
Giuppox
  • 1,393
  • 9
  • 35
  • 2
    I would be surprised if this worked. You are promising to return a `D&` but istead return a `D[S]`. Even then you would return a reference to a local array and might want to return `std::array` instead. – Lukas-T Feb 08 '21 at 09:02
  • @churill yeah, noticed that now, among other things. – Giuppox Feb 08 '21 at 09:11
  • Please refrain from using the C tag for "C style" C++ code. See tag usage wiki for the C and C++ tags for details. – Lundin Feb 08 '21 at 09:19
  • Is it always `int a[x][y]` or can it be `int arr[x][y][z]` and others? – Aykhan Hagverdili Feb 08 '21 at 09:35
  • @AyxanHaqverdili is should have arbitrary dimensions, so it can be `int arr[x][y]` or also `int arr[x][y][z][k]` – Giuppox Feb 08 '21 at 10:12

2 Answers2

3

So, here's one way of doing it:

#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdio>
#include <type_traits>

// array_type_swap convert T[a][b][c][...] to Y[a][b][c][...] for arbitrary
// dimensions
template <class T, class Y>
struct array_type_swap {
  using type = T;
};

template <class T, class Y, std::size_t N>
struct array_type_swap<T, std::array<Y, N>> {
  using type = std::array<typename array_type_swap<T, Y>::type, N>;
};

template <class T, class Y, std::size_t N>
struct array_type_swap<T, Y[N]> {
  using type = typename array_type_swap<T, std::array<Y, N>>::type;
};

template <class T, class Y>
using array_type_swap_t = typename array_type_swap<T, Y>::type;

template <class>
inline constexpr bool is_std_array_v = false;

template <class T, std::size_t N>
inline constexpr bool is_std_array_v<std::array<T, N>> = true;

// Get element count of a std::array, or C style array as constexpr. The value
// is 1 for all other types (as in array of 1).
template <class T>
struct contexpr_array_size {
  static std::size_t constexpr size = 1;
};

template <class T, std::size_t N>
struct contexpr_array_size<T[N]> {
  static std::size_t constexpr size = N;
};

template <class T, std::size_t N>
struct contexpr_array_size<std::array<T, N>> {
  static std::size_t constexpr size = N;
};

template <class T>
inline auto constexpr contexpr_array_size_v = contexpr_array_size<T>::size;

template <class T, class Y>
void copy_array(T& dst, Y const& src) {
  static_assert(contexpr_array_size_v<T> == contexpr_array_size_v<Y>,
                "Can only copy arrays of the same size");
  if constexpr (!is_std_array_v<Y> && !std::is_array_v<Y>)
    dst = static_cast<T>(src);
  else
    for (std::size_t i = 0; i < contexpr_array_size_v<Y>; ++i)
      copy_array(dst[i], src[i]);
}

template <class T, class Y>
auto uniform(Y const& arr) {
  array_type_swap_t<T, Y> result;

  copy_array(result, arr);

  return result;
}

int main() {
  std::puts("built-in array :");

  const int a[2][3] = {{1, 2, 3}, {4, 5, 6}};

  auto b = uniform<float>(a);

  for (auto& row : b) {
    for (auto& elm : row) std::printf("%.2f ", elm);
    std::putchar('\n');
  }

  std::puts("\nstd::array :");
  std::array<std::array<int, 3>, 2> stdarr = {{{6, 5, 4}, {3, 2, 1}}};

  b = uniform<float>(stdarr);

  for (auto& row : b) {
    for (auto& elm : row) std::printf("%.2f ", elm);
    std::putchar('\n');
  }
}
Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
  • hi, this is really good actually, and quite difficult! I saw that `uniform` returns a `std::array` object. Is it possible to make it return a raw array? – Giuppox Feb 08 '21 at 11:04
  • I suggest `std::copy_n(src, cnt, dst);` instead - but I wonder if this isn't UB. Even if we _know_ that the data must be there, it's reading and writing out-of-bounds. (I use this shortcut myself - but I suspect that it's "wrong"). – Ted Lyngmo Feb 08 '21 at 11:07
  • @Giuppox I have not used `std::array` in this answer. In C++, functions are not allowed to return raw-arrays directly. But functions can return arrays in a struct. This is exactly what my answer does. Returns a struct with a single element, the array. – Aykhan Hagverdili Feb 08 '21 at 11:07
  • @TedLyngmo Thank you for the suggestion! After casting to a single element pointer, like `int*`, we are no longer working with an array, but with a pointer. So, there is no 'out of bounds' (bounds of what?), and it is fine as long as we access valid addresses in the memory. That's my understanding of it anyway. Maybe that could be a nice question to ask here. – Aykhan Hagverdili Feb 08 '21 at 11:13
  • @TedLyngmo I asked it [here](https://stackoverflow.com/q/66100693/10147399) – Aykhan Hagverdili Feb 08 '21 at 11:24
  • @AyxanHaqverdili "_bounds of what_" - That would be the outer array since `&result.value` is a `float(*)[2][3]`, not a `float*` (and afaik `reinterpret_cast` doesn't change that) - but I'm not sure so it's great that you opened a question for that. – Ted Lyngmo Feb 08 '21 at 11:38
  • can you show how to make the function return a `std::array` object? I think it is a bit more elegant that returning a struct with a raw array inside... – Giuppox Feb 08 '21 at 13:05
  • @Giuppox you want the argument and the return type both be a `std::array`? – Aykhan Hagverdili Feb 08 '21 at 13:07
  • i mean, is it possible to make the function accept as argument both `std::array` and raw arrays, and return `std::array`? – Giuppox Feb 08 '21 at 13:09
  • @Giuppox here you go. This converts both c-arrays and std::array into a std::array of a different type. – Aykhan Hagverdili Feb 08 '21 at 13:39
  • 1
    @TedLyngmo this New Version™ avoids the UB reinterpret_cast. It even produces shorter assembly on both clang and gcc. – Aykhan Hagverdili Feb 08 '21 at 14:47
0

You can't return [] arrays from a function, so the 1D version doesn't work.

You can use std::array.

template <class D, class T, std::size_t S1, std::size_t S2>
inline std::array<std::array<D, S1>, S2> uniform(std::array<std::array<T, S1>, S2> t) {
    std::array<std::array<D, S1>, S2> v;
    std::array<D, S1>(*inner)(std::array<T, S1>) = uniform<D, T, S1>;
    std::transform(t.begin(), t.end(), v.begin(), inner);
    return v;
}

template <class D, class T, std::size_t S>
inline std::array<D, S> uniform(std::array<T, S> t) {
    return { t.begin(), t.end() };
}
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • hi, thanks for answering my question. What's the difference between `D x[S]` and `std::array x`? – Giuppox Feb 08 '21 at 09:19
  • `std::array` is a class type that contains an array as a member. It's copyable and assignable, whereas raw arrays aren't – Caleth Feb 08 '21 at 09:22
  • @Caleth but, once an instance of `std::array` is created shouldn't it be equal to a raw array? – Giuppox Feb 08 '21 at 09:26
  • It does the same things as raw arrays, except that there isn't an implicit conversion to pointer (it has a member `data()` for that). Additionally, it is a copyable and assignable type. – Caleth Feb 08 '21 at 09:33
  • @Caleth still doesn't compile https://gcc.godbolt.org/z/8q66Gq – Aykhan Hagverdili Feb 08 '21 at 09:39
  • @AyxanHaqverdili given up on the arbitrary dimension one, can't figure how to deduce the result type without more coffee – Caleth Feb 08 '21 at 09:56