nachum
nachum

Reputation: 567

convert constexpr n-dimensional array of one type to constexpr n-dimensional array of a different type

Using C++11 or C++14:

I have a:

constexpr double [2][3][4] = some value;

I want constexpr int [2][3][4] from this.

(Actually I will want constexpr my_type [2][3][4], but that should be trivially solvable once int works.)

The only solution I came up with was a container class with operator[] overloaded so it behaves like an array. Is there a way to keep standard array syntax? Or possibly std::array<std::array...>> without creating a new class?

I do not want the input to be std::array - that is a constraint of the system. The input must be C compatible. The converted array can be C++.

Upvotes: 2

Views: 117

Answers (3)

max66
max66

Reputation: 66230

If you accept a constexpr std::array<std::array<int, 4U>, 3U>, 2U>, well... using SFINAE tag-dispatching, std::rank, decltype(), auto as return type and std::index_sequence (so a C++14 solution) you can ping-pong play with a getArray() and his helper getArrayH() functions as follows

NOTE: answer modified (improved, IMHO) taking inspiration from a Julius's example (thanks!)

#include <array>
#include <iostream>
#include <type_traits>

template <typename T>
std::integral_constant<bool, std::rank<T>::value != 0U> isArray ();

template <typename targetT, typename origT, std::size_t Dim>
constexpr auto getArray (origT(&)[Dim]);

template <typename targetT, typename arrT, std::size_t ... Is>
constexpr auto getArrayH (arrT const & arr,
                          std::false_type const &,
                          std::index_sequence<Is...> const &)
 { return std::array<targetT, sizeof...(Is)>{ { targetT(arr[Is])... } }; }

template <typename targetT, typename arrT, std::size_t ... Is>
constexpr auto getArrayH (arrT const & arr,
                          std::true_type const &,
                          std::index_sequence<Is...> const &)
 { return std::array<decltype(getArray<targetT>(arr[0])), sizeof...(Is)>
           { { getArray<targetT>(arr[Is])... } }; }

template <typename targetT, typename origT, std::size_t Dim>
constexpr auto getArray (origT(&arr)[Dim])
 { return getArrayH<targetT>(arr, decltype(isArray<origT>()){},
                             std::make_index_sequence<Dim>{}); }

int main ()
 {
   constexpr double ad3[2][3][4]
    { { { 1.0, 2.0, 3.0, 4.0 },
        { 2.1, 3.1, 4.1, 5.1 },
        { 3.2, 4.2, 5.2, 6.2 } },
      { { 6.3, 5.3, 4.3, 3.3 },
        { 5.4, 4.4, 3.4, 2.4 },
        { 4.5, 3.5, 2.5, 1.5 } } };

   for ( auto const & ad2 : ad3 )
      for ( auto const & ad1 : ad2 )
         for ( auto const & ad0 : ad1 )
            std::cout << ad0 << ' ';

   std::cout << std::endl;

   constexpr auto ai3 = getArray<int>(ad3);

   static_assert(std::is_same<decltype(ai3),
      std::array<std::array<std::array<int, 4U>, 3U>, 2U> const>{}, "!");

   for ( auto const & ai2 : ai3 )
      for ( auto const & ai1 : ai2 )
         for ( auto const & ai0 : ai1 )
            std::cout << ai0 << ' ';

   std::cout << std::endl;
 }

Upvotes: 2

Julius
Julius

Reputation: 1861

Unfortunately, std::array::operator[] is not constexpr before C++17. Hence you probably need to come up with your own boilerplate.

Here is an example in C++17. If you export the "indices trick" into a separate function (instead of lambda) and implement your own version of std::array, then this approach should also work with C++14.

https://godbolt.org/g/FayqEn

#include <array>
#include <iostream>
#include <utility>

////////////////////////////////////////////////////////////////////////////////

template<size_t... is, class F>
constexpr decltype(auto) indexer(std::index_sequence<is...>, F f) {
  return f(std::integral_constant<std::size_t, is>{}...);
}

template<size_t N_, class F>
constexpr decltype(auto) indexer(F f) {
  constexpr size_t max_index_length = 4096;
  constexpr size_t N = std::min(N_, max_index_length);
  static_assert(N == N_, "");
  return indexer(std::make_index_sequence<N>{}, f);
}

////////////////////////////////////////////////////////////////////////////////

template<class T>
constexpr auto make_array(T val) {
  return val;
}

template<class NestedArrays, std::size_t N>
constexpr auto make_array(NestedArrays(&arr)[N]) {
  using NestedStdArrays = decltype(make_array(arr[0]));
  return indexer<N>([=] (auto... ns) {
    return std::array<NestedStdArrays, N>{
      make_array(arr[ns])...
    };
  });
}

////////////////////////////////////////////////////////////////////////////////

int main() {
  constexpr int input_from_c[1][3][2]{
    { {0, 10}, {20, 30}, {40, 50} }
  };

  constexpr auto nested_std_arrays = make_array(input_from_c);
  using NestedStdArrays = std::decay_t<decltype(nested_std_arrays)>;

  static_assert(
    std::is_same<
      NestedStdArrays,
      std::array<std::array<std::array<int, 2>, 3>, 1>
    >{}, ""
  );

  static_assert(0 == nested_std_arrays[0][0][0], "");
  static_assert(10 == nested_std_arrays[0][0][1], "");
  static_assert(20 == nested_std_arrays[0][1][0], "");
  static_assert(30 == nested_std_arrays[0][1][1], "");
  static_assert(40 == nested_std_arrays[0][2][0], "");
  static_assert(50 == nested_std_arrays[0][2][1], "");

  return 0;
}

Upvotes: 0

Dimitrios Bouzas
Dimitrios Bouzas

Reputation: 42929

There's a way to do this by building incrementally. That is, first solve the conversion for 1D arrays. Then based on that solution the conversion for 2D arrays etc.

template<typename U, typename T, std::size_t N, std::size_t... I>
constexpr
std::array<U, N>
convert1DArrayImpl(std::array<T, N> const &a, std::index_sequence<I...>) {
  return {static_cast<U>(a[I])...};
}

template<typename U, typename T, std::size_t N, typename Indices = std::make_index_sequence<N>>
constexpr
std::array<U, N>
convert1DArray(const std::array<T, N>& a) {
  return convert1DArrayImpl<U>(a, Indices{});
}

template<typename U, typename T, std::size_t N, std::size_t M, std::size_t... IN>
constexpr
std::array<std::array<U, N>, M>
convert2DArrayImpl(std::array<std::array<T, N>, M> a, std::index_sequence<IN...>) {   
  return {convert1DArray<U>(a[IN])...};
}

template<typename U, typename T, std::size_t N, std::size_t M, typename IndicesM = std::make_index_sequence<M>>
constexpr
std::array<std::array<U, N>, M>
convert2DArray(const std::array<std::array<T, N>, M>& a) {
  return convert2DArrayImpl<U>(a, IndicesM{});
}

template<typename U, typename T, std::size_t N, std::size_t M, std::size_t K, std::size_t... IN>
constexpr
std::array<std::array<std::array<U, N>, M>, K>
convert3DArrayImpl(std::array<std::array<std::array<T, N>, M>, K> a, std::index_sequence<IN...>) {   
  return {convert2DArray<U>(a[IN])...};
}

template<typename U, typename T, std::size_t N, std::size_t M, std::size_t K, typename IndicesK = std::make_index_sequence<K>>
constexpr
std::array<std::array<std::array<U, N>, M>, K>
convert3DArray(const std::array<std::array<std::array<T, N>, M>, K>& a) {
  return convert3DArrayImpl<U>(a, IndicesK{});
}

Live Demo

Upvotes: 2

Related Questions