convert c multidimensional array to multidimensional c++ vector - c++

i am trying to convert c multidimensional array to multidimensional c++ vector, i mean, to convert something like this int arr[2][3] = {{1,2,3}, {4,5,6}}; into the correspondent vector.
The array isn't necessarily 2D shaped, it could also be something like this:
int arr[2][2][3] = {
{
{1,2,3},
{4,5,6},
},
{
{7,8,9},
{10,11,12},
}
};
initially i thought that something like this would have worked, but it didn't really turned out to be the case because it seems like if std::vector doesn't allows conversions from C arrays.
std::vector<std::any> V(arr);
Then i thought at something like function recursion, this is my attempt, that (i don't know why!) throws error: no matching function for call to 'length' .
#include <iostream>
#include <type_traits>
#include <vector>
#include <any>
// Get the lenght of a classic C array.
template <class T, unsigned S>
inline unsigned length(const T (&v)[S]) {
return S;
};
// Check wether the input is a classic C array or not.
template <class T>
bool is(const T& t) {
return std::is_array_v<T>;
};
// Turn the classic C input array to vector.
template <class T>
std::vector<std::any> toVector(const T& t) {
std::vector<std::any> V;
for (int k = 0; k < length(t); k++) {
if (is(t[k])) {
V.push_back(toVector(t[k]));
} else {
V.push_back(t[k]);
}
}
return V;
}
int main() {
int16 a[] = {1,2,3};
auto b = toVector(a);
}
What did i wrong in the second attempt? Alternatively, is there a simpler way to manage to do this?
Also, i think it would be better to convert all the numbers in the vector to a unique given data type, is this possible?
I am using c++11 and g++ as compiler –
Note that i do not know how many dimensions my array has.

The equivalent of a C multidimensional array is a flat C++ vector plus a mumtidimensional array view.
The naive equivalent is a vector of vectors (maybe of vectors), which actually corresponds to a C "jagged" array. The memory layout and performance characteristics will be very different.
There are many multi dimensional array-view implementstions on the web.
template<class A>
struct raw_ptr { using type=A*; };
template<class A, std::size_t N>
struct raw_ptr<A[N]>:raw_ptr<A>{};
template<class A>
using raw_ptr_t = typename raw_ptr<A>::type;
template<class T>
struct array_view;
template<class T, std::size_t D0, std::size_t D1>
struct array_view<T[D0][D1]> {
T(*data)[D1]=0;
constexpr array_view( T(&arr)[D0][D1] ):data(arr) {}
explicit array_view( raw_ptr_t<T> buff ):data(reinterpret_cast<T(*)[D1]>(buff)) {}
constexpr array_view<T[D1]> operator[](std::size_t i)const{
return data[i];
}
};
template<class T, std::size_t D0>
struct array_view<T[D0]> {
constexpr array_view( T(&arr)[D0] ):data(arr) {}
explicit constexpr array_view( T* buff ):data(buff) {}
T* data=0;
constexpr T& operator[](std::size_t i)const{
return data[i];
}
};
to convert a int[4][5][6] you'd do:
int array[4][5][6]={/*whatever*/};
std::vector<int> buff(&array[0][0][0], &array[3][4][5]);
array_view<int[4][5][6]> view{buff.data()};
now, view[a][b][c] is the same as array[a][b][c].
Live example.
template<class Array>
struct wrap_array_in_vector {
using raw_ptr = raw_ptr_t<Array>;
using value_type = std::remove_pointer_t<raw_ptr>;
std::vector<value_type> data;
array_view<Array> view;
wrap_array_in_vector(wrap_array_in_vector const& other):
data(other.data),
view(data.data())
{}
wrap_array_in_vector(wrap_array_in_vector && other):
data(std::move(other.data)),
view(data.data())
{
other.view.data = nullptr; // no longer valid
}
decltype(auto) operator[](std::size_t i)const {
return view[i];
}
wrap_array_in_vector( Array const& arr ):
data( reinterpret_cast<value_type const*>(&arr[0]), reinterpret_cast<value_type const*>(&arr[1]) ),
view(data.data())
{}
};
template<class Array>
wrap_array_in_vector(Array&)->wrap_array_in_vector<Array>;
template<class Array>
wrap_array_in_vector(Array const&)->wrap_array_in_vector<Array>;
this lets you do
wrap_array_in_vector wrapped = array;
and wrapped deduces all of its type information it needs.

This should be close to optimal for conversion to a vector. The part of the description that was misleading was that the sizes are not regular, as they are list-initialized and in this case with int, will be zeros.
#include <type_traits>
#include <vector>
#include <iostream>
template<typename T, std::size_t N>
auto to_vectors( T const (&c_array)[N] ) {
using element_base_type = std::decay_t<T>;
if constexpr( std::is_array_v<T> ) {
using child_t = std::remove_cv_t<std::remove_reference_t<decltype( to_vectors( c_array[0] ) )>>;
auto result = std::vector<child_t>( );
result.reserve( N );
for( auto const & element: c_array ) {
result.push_back( to_vectors( element ) );
}
return result;
} else {
return std::vector<T>( c_array, c_array + N );
}
}
int arr[2][2][3] = {
{
{1,2,3},
{4,5,6},
},
{
{7,8,9},
}
};
auto v0 = to_vectors( arr );
template<typename Vec>
void display( Vec const & vec ) {
if constexpr( std::is_same_v<int, typename Vec::value_type> ) {
std::cout << "elements: {";
for( int e: vec ) {
std::cout << e << ',';
}
std::cout << "}\n";
} else {
std::cout << "element count: " << vec.size( ) << '\n';
for( auto const & child: vec ) {
display( child );
}
}
}
int main( ) {
display( v0 );
}
this will output
element count: 2
element count: 2
elements: {1,2,3,}
elements: {4,5,6,}
element count: 2
elements: {7,8,9,}
elements: {0,0,0,}

Related

Expand parameter pack into tuple with tuple_cat

Godbolt link: https://godbolt.org/z/18nseEn4G
I have a std::map of various types of vectors (cast to void*) and a T& get<T> method that gives me a reference to an element in one of the vectors in the map.
class Container {
public:
Container() {
auto v1 = new std::vector<int>({1, 2, 3, 4, 5});
auto v2 = new std::vector<char>({'a','b','c','d','e'});
auto v3 = new std::vector<double>({1.12, 2.34, 3.134, 4.51, 5.101});
items.insert({
std::type_index(typeid(std::vector<int>)),
reinterpret_cast<void*>(v1)
});
items.insert({
std::type_index(typeid(std::vector<char>)),
reinterpret_cast<void*>(v2)
});
items.insert({
std::type_index(typeid(std::vector<double>)),
reinterpret_cast<void*>(v3)
});
}
template<typename T>
T& get(int index) {
auto idx = std::type_index(typeid(std::vector<T>));
auto ptr = items.at(idx);
auto vec = reinterpret_cast<std::vector<T>*>(ptr);
return (*vec)[index];
}
private:
std::map<std::type_index, void*> items {};
};
I want to be able to use structured binding to get back references to 3 elements all at the same index but in difference vectors, but I'm not sure how to create a tuple with multiple calls to the T& get<T> method.
Something like this;
auto [a, b, c] = myContainer.get_all<int, char, double>(1); // get a reference to an int, a char, and a double from myContainer at index 1.
I'm currently trying to make use of repeated calls to T& get<T> for each parameter in a parameter pack, but I can't figure out the correct syntax.
template<typename... Ts>
auto get_all(int index) {
return std::tuple_cat<Ts...>(
std::make_tuple<Ts>(get<Ts>(index)...)
);
How could I make this work?
Here is a link to my current attempt:
https://godbolt.org/z/18nseEn4G
Alternatively, is there a "better way" to achieve this?
I would suggest using type erasure. Here is an example:
#include <vector>
#include <typeindex>
#include <memory>
#include <any>
#include <unordered_map>
#include <iostream>
#include <experimental/propagate_const>
// If no library implementation is availble, one may be copied from libstdc++
template<class T>
using propagate_const = std::experimental::propagate_const<T>;
class Container
{
public:
Container() {
std::unique_ptr<Eraser> v1{ static_cast<Eraser*>(new ErasedVector<int>(1, 2, 3, 4, 5)) };
std::unique_ptr<Eraser> v2{ static_cast<Eraser*>(new ErasedVector<char>('a','b','c','d','e')) };
std::unique_ptr<Eraser> v3{ static_cast<Eraser*>(new ErasedVector<double>(1.12, 2.34, 3.134, 4.51, 5.101)) };
items[std::type_index(typeid(int))] = std::move(v1);
items[std::type_index(typeid(char))] = std::move(v2);
items[std::type_index(typeid(double))] = std::move(v3);
}
template<typename... Ts>
std::tuple<Ts&...> get(size_t index)
{
return {
std::any_cast<std::reference_wrapper<Ts>>((*items.find(std::type_index{typeid(Ts)})->second)[index]).get()...
};
}
template<typename... Ts, typename = std::enable_if_t<(std::is_const_v<Ts> && ...)>>
std::tuple<Ts&...> get(size_t index) const
{
return {
std::any_cast<std::reference_wrapper<Ts>>((*items.find(std::type_index{typeid(Ts)})->second)[index]).get()...
};
}
private:
class Eraser
{
public:
virtual std::any operator[](size_t index) = 0;
virtual std::any operator[](size_t index) const = 0;
virtual ~Eraser() = default;
};
template <typename T>
class ErasedVector : public Eraser
{
public:
template <typename... Args>
ErasedVector(Args&&... args) :
data{ std::forward<Args>(args)... }
{
}
virtual std::any operator[](size_t index) override final
{
return std::reference_wrapper{ data[index] };
};
virtual std::any operator[](size_t index) const override final
{
return std::reference_wrapper{ data[index] };
}
private:
std::vector<T> data;
};
std::unordered_map<std::type_index, propagate_const<std::unique_ptr<Eraser>>> items;
};
It works properly on this example:
int main()
{
Container co;
auto [i0_0, c0_0, d0_0] = co.get<int, char, double>(0);
std::cout << i0_0 << ' ' << c0_0 << ' ' << d0_0 << '\n';
i0_0 = 3; // is a reference
d0_0 = 42; // is a reference
auto [i0_1, d0_1] = static_cast<const Container&>(co).get<const int, const double>(0); // works on const Container
std::cout << i0_1 << ' ' << d0_1; // original values modified
// i0_1 = 0xDEADBEEF; can be const too
}
And outputs:
1 a 1.12
3 42
Demo
Simply:
template<typename... Ts>
auto get_all(int index) {
return std::tuple<Ts&...>(get<Ts>(index)...);
}
Demo
You can use std::tie to take all of the "returns" from get<Ts>(index) can pack them into a tuple of references. That would look like
template<typename... Ts>
auto get_all(int index) {
return std::tie(get<Ts>(index)...);
}

How to use Variadic Function with strings, without using templates?

I have the following template function:
struct ms {
template <typename... Args>
void update(string& query, Args&... args);
};
template <typename... Args>
void ms::update(string& query, Args&... args)
{
const int size = sizeof...(args);
vector<string> vec = { args... };
for (int i = 0; i < size; ++i) {
cout << query << ": " << vec[i] << endl;
}
}
However, I would like to eliminate the use of template and simply make this a member function that takes one or more string arguments. All of the documentation and examples on Variadic Functions I can find show use of character arrays and doing a while pointer != null to grab each value in the array.
If I do something like:
void update(string& query, string&... args);
how would I iterate over the args parameters, if there are any?
This is an array_view<T>:
template<class T>
struct array_view;
template<class D>
struct array_view_base;
template<class T>
struct array_view_base<array_view<T>> {
T* b=0; T* e=0;
T* begin() const { return b; }
T* end() const { return e; }
T& operator[](std::size_t i)const{ return begin()[i]; }
std::size_t size() const { return end()-begin(); }
T& front() const { return *begin(); }
T& back() const { return *(end()-1); }
array_view<T> without_front( std::size_t N=1 ) const {
N=(std::min)(N, size());
return {begin()+N, end()};
}
array_view<T> without_back( std::size_t N=1 ) const {
N=(std::min)(N, size());
return {begin(), end()-N};
}
array_view_base( T* s, T* f ):b(s),e(f){}
array_view_base( T* s, std::size_t sz ):array_view_base(s, s+sz) {}
template<std::size_t N>
array_view_base( T(&arr)[N] ):array_view_base(arr, N) {}
template<class C,
std::enable_if_t<!std::is_same<std::decay_t<C>, array_view<T>>{}, int> =0
>
array_view_base( C&& c ):array_view_base(c.data(), c.size()) {}
};
template<class T>
struct array_view:array_view_base<array_view<T>> {
using array_view_base<array_view<T>>::array_view_base;
};
template<class T>
struct array_view<T const>:array_view_base<array_view<T const>> {
using array_view_base<array_view<T const>>::array_view_base;
array_view( std::initializer_list<T> il ):array_view( std::addressof(*il.begin()), il.size() ) {}
};
it works a bit like a gsl::span<T>. it is a contiguous range of T.
Unlike gsl::span<T>, array_view<T const> can be constructed from an initializer_list<T>.
With it your code should look like:
struct ms {
void update(string const& query, array_view<string const> args);
};
void ms::update(string const& query, array_view<string const> args)
{
for (int i = 0; i < args.size(); ++i) {
cout << query << ": " << args[i] << endl;
}
}
and you call it like:
ms{}.update( "Hello", {"one", "two", "three"} );
Live example.
As a worse example, simply take a std::vector<std::string>.
At point of call
ms{}.update( "Hello", {"one", "two", "three"} );
also works. Unlike my solution, this causes a memory allocation.

Using part of enum as array index

I have large enum for example:
enum { elem0, elem1, elem2, elem3 ...... elem1000 }
I would like to create an array of float numbers using just some of the enum elements.
For example, I would like to have only three elements in array: elem0, elem7, elem999 and I would like to acccess them in similar style as this:
array[elem0]=123
array[elem7]=12.5
array[elem999]=15.6
What would be the most elegant way to implement such an array so that array is going to have only three elements ?
Just write a conversion from enum to array index:
int EnumToIdx(Elem elem)
{
switch (elem)
{
case elem0: return 0;
case elem7: return 1;
case elem999: return 2;
}
throw std::invalid_argument("EnumToIdx: no conversion"); // or whatever
}
Usage
array[EnumToIdx(elem0)] = 123;
array[EnumToIdx(elem7)] = 12.5;
array[EnumToIdx(elem999)] = 15.6;
Maybe overkill, but then again maybe not - template expansion can write conversion functions for you.
This template class allows you to specify which elems should be valid for the vector, and in which order.
It also provides the ability to index into the vector by elem, either dynamically (throw exception if does not exists) or statically (fail to compile if out of bounds).
#include <array>
#include <iostream>
#include <algorithm>
#include <iterator>
#include <stdexcept>
//
// boilerplate
//
namespace notstd {
template<class T, class Tuple>
struct tuple_index;
template<class T, class... Types>
struct tuple_index<T, std::tuple<T, Types...>> {
using type = std::size_t;
static const type value = 0;
};
template<class T, class U, class... Types>
struct tuple_index<T, std::tuple<U, Types...>> {
using type = std::size_t;
static const type value = 1 + tuple_index<T, std::tuple<Types...>>::value;
};
}
enum element_type {
elem0, elem1, elem2, elem3, elem4, elem5, elem1000
};
template<element_type Value>
struct where_type
{
static const element_type value = Value;
};
template<element_type Value>
static constexpr auto where = where_type<Value> {};
template<element_type...Permitted>
struct restricted_vector : private std::array<double, sizeof...(Permitted)> {
using corresponding_tuple = std::tuple<where_type<Permitted>...>;
using inherited = std::array<double, sizeof...(Permitted)>;
using inherited::inherited;
static auto conversion(element_type e) -> std::size_t
{
static const std::array<std::pair<element_type, std::size_t>, sizeof...(Permitted)> a = {
std::make_pair(Permitted, notstd::tuple_index<where_type<Permitted>, corresponding_tuple>::value)...
};
auto ifind = std::find_if(a.begin(), a.end(), [e](auto&& elem) { return elem.first == e; });
if (ifind == a.end()) {
throw std::out_of_range("invalid element");
}
return ifind->second;
}
template<element_type Elem>
auto& operator[](where_type<Elem>) {
auto pos = notstd::tuple_index<where_type<Elem>, corresponding_tuple >::value;
return inherited::operator[](pos);
}
template<element_type Elem>
auto const& operator[](where_type<Elem>) const {
auto pos = notstd::tuple_index<where_type<Elem>, corresponding_tuple >::value;
return inherited::operator[](pos);
}
// dynamic access
auto& at(element_type e) {
return inherited::operator[](conversion(e));
}
auto const& at(element_type e) const {
return inherited::operator[](conversion(e));
}
using inherited::begin;
using inherited::end;
using inherited::size; // etc
};
int main() {
auto v1 = restricted_vector<elem3, elem4, elem5> {};
v1[where<elem4>] = 0.4;
v1[where<elem5>] = 0.5;
v1[where<elem3>] = 0.3;
std::copy(v1.begin(), v1.end(), std::ostream_iterator<double>(std::cout, ", "));
std::cout << "\n";
std::cout << "at elem4: " << v1.at(elem4) << std::endl;
}
expected output:
0.3, 0.4, 0.5,
at elem4: 0.4

Resize recursive nested vector in template

I'm trying to make a template function that resizes a nested vector in all it's dimensions.
Pretty much like this: resizing multidimensional vector , but for an arbitrary nr. of dims.
(I suppose) The function would (at least) accept a reference to the vector (or vector<vector<T>> or v<v<v<T>>>, etc.) and a vector with the desired sizes. I now also have in index in the sizes vector, but it's probably not needed.
So far, this is what I ended up with (could be completely wrong):
template<typename V> void resize(vector<V> & V1, vector<int32_t> t, int32_t index) {
int32_t current_size=t.at(index);
cout << "resize dim [" << index << "] to size " << current_size <<endl ;
++index;
if (index < t.size()) {
// for each element ??
// for( int i = 0 ; i < V1.size(); i++ ) { resize (V1.at(i), t, index); } // doesn't work
// for( auto const& e : V1 ) { resize (e, t, index); } // doesn't work
// resize( V1, t, index); // recursive call, works, but doesn't do anything
}
I'd like to avoid copies of V1 or any of it's content. I'm not sure if there is a way with an iterator or for loop that passes references. If not, there probably needs to be a forth input to keep the index of V1?
Btw., I'm skipping the first dim on purpose, it is already the correct size.
Any help appreciated.
You're probably looking for something like this (this is c++14-compliant solution, similar one would be a little bit more tricky for c++11 but still possible):
#include <vector>
#include <tuple>
#include <utility>
template <class NestedVectorElement, class Tuple>
void nested_resize(std::vector<std::vector<NestedVectorElement>> &v, Tuple &&t);
template <class VectorElement, class Tuple>
void nested_resize(std::vector<VectorElement> &v, Tuple &&t);
template <class Vector, class Tuple, size_t... Is>
void nested_resize_impl(Vector &v, Tuple &&t, std::index_sequence<Is...>) {
v.resize(std::get<0>(t));
for(auto &nv: v) {
nested_resize(nv, std::forward_as_tuple(std::get<Is + 1>(t)...));
}
}
template <class NestedVectorElement, class Tuple>
void nested_resize(std::vector<std::vector<NestedVectorElement>> &v, Tuple &&t) {
nested_resize_impl(v, t, std::make_index_sequence<std::tuple_size<Tuple>::value - 1>{});
}
template <class VectorElement, class Tuple>
void nested_resize(std::vector<VectorElement> &v, Tuple &&t) {
v.resize(std::get<0>(t));
}
int main() {
std::vector<std::vector<std::vector<int>>> matrix;
nested_resize(matrix, std::make_tuple(3, 2, 3));
matrix.at(2).at(1).at(2) = 0; // at gives you an access only if element exists else throws an exception
}
The real problem here is that each instance of the template needs to generate code for two possibilities: the last dimension of the multidimensional vector, and all other dimensions of the vector. And in the case of the latter, it is necessary to recurse over the following dimensions of the vector, which will lead to an obvious compilation error in the case of the former.
This requires specialization:
#include <vector>
#include <iostream>
template<typename V, typename iter_type>
class resize_dim {
public:
static void resize(V & V1,
iter_type b, iter_type e)
{
if (b == e)
return;
V1.resize(*b);
}
};
template<typename V, typename iter_type>
class resize_dim<std::vector<std::vector<V>>, iter_type> {
public:
static void resize(std::vector<std::vector<V>> & V1,
iter_type b, iter_type e)
{
if (b == e)
return;
V1.resize(*b);
++b;
for (typename std::vector<std::vector<V>>::iterator
vb=V1.begin(),
ve=V1.end(); vb != ve; ++vb)
resize_dim<std::vector<V>, iter_type>::resize(*vb, b, e);
}
};
template<typename V>
void resize(V &v, const std::vector<size_t> &dimensions)
{
resize_dim<V, std::vector<size_t>::const_iterator>
::resize(v, dimensions.begin(), dimensions.end());
}
int main()
{
std::vector<std::vector<std::vector<int>>> v;
std::vector<size_t> d;
d.push_back(3);
d.push_back(3);
d.push_back(3);
resize(v, d);
std::cout << "Ok" << std::endl;
return 0;
}
The sizing vector, giving the size of each dimension should match the number of dimensions in the vector. Extra sizes are ignored. Fewer sizes result only in the leading dimensions getting resized.
This works:
template<typename V> void resizer(V & V1, vector<int32_t> const & t, int32_t index) {}
template<typename V> void resizer(vector<V> & V1, vector<int32_t> const & t, int32_t index) {
int32_t current_size=t.at(index);
V1.resize(t.at(index) );
++index;
if (index < t.size() ) {
for( auto & e : V1 ) {
resizer (e , t, index);
}
}
}
But this is actually a bit better, since we're not needlessly iterating over the last dimension's elements:
template<typename V> void resizer(vector<V> & V1, vector<int32_t> const & t, int32_t index) {
int32_t current_size=t.at(index);
V1.resize(t.at(index) );
}
template<typename V> void resizer(vector<std::vector<V>> & V1, vector<int32_t> const & t, int32_t index) {
int32_t current_size=t.at(index);
V1.resize(t.at(index) );
++index;
if (index < t.size() ) {
for( auto & e : V1 ) {
resizer (e , t, index);
}
}
}

Differentiate between 1D and 2D container in template class constructor (SFINAE)

So, I have a class, which has an array of arrays as a private member. I wish to have two constructors for each case (1D or 2D). But of course their declaration happens to be the same, so template deduction can't do its job without me doing something about it. Here's the code:
Edit: I also need it to work with STL containers like vector or C++ array. That is why I am overcomplicating and not going with the "arrays" fix.
#include <iostream>
#include <array>
template<class T, std::size_t rows_t, std::size_t cols_t>
class test
{
private:
std::array<std::array<T, cols_t>, rows_t> _data;
public:
auto begin() { return this->_data.begin(); }
auto end() { return this->_data.end(); }
//CONSTRUCTOR
template<class type_t>
test(const type_t &arr)
{
std::size_t j = 0;
for (const auto &num : arr)
this->_data[0][j++] = num;
}
template<class type_t>
test(const type_t &arr)
{
std::size_t i = 0;
for (const auto &el : arr)
{
std::size_t j = 0;
for (const auto &num : el)
this->_data[i][j++] = num;
++i;
}
}
};
int main()
{
double arr[3] = { 1, 2, 3 };
double arr2[2][2] = { {1, 2}, {3, 4} };
test<double, 1, 3> obj = arr;
test<double, 2, 2> obj2 = arr2;
for (const auto &i : obj2)
{
for (const auto &j : i)
std::cout << j << " ";
std::cout << std::endl;
}
std::cin.get();
}
Note: I've been reading about enable_if, but I don't quite understand how it works. Can it be done with that?
The constructors should not be the same, but you have only provided the most generic matching possible.
SFINAE is not necessary here. Just provide a constructor for a 1D array, and a separate constructor for a 2D array:
template <typename T2, std::size_t N>
test( const T2 (&a)[N] )
{
...
}
template <typename T2, std::size_t M, std::size_t N>
test( const T2 (&a)[M][N] )
{
...
}
Another note: POSIX reserves typenames ending with "_t", so it is typically a good idea to avoid them in your own code. (Obnoxious, I know.) Standard C++ will use Camel Case of the form: RowsType, etc, and then typedef a rows_type for users of the class.
Notice, however, that rows_t is not actually a type -- it is a value. A better name would be something like NRows.
Hope this helps.
First, you have to "teach" the compiler what's 2D and what's not. Hence, you have to define something like the following type trait:
template<typename T>
struct is2D : public std::false_type {};
template<typename T, std::size_t N, std::size_t M>
struct is2D<std::array<std::array<T, M>, N>> : std::true_type {};
template<typename T>
struct is2D<std::vector<std::vector<T>>> : std::true_type {};
template<typename T, std::size_t N, std::size_t M>
struct is2D<T[N][M]> : std::true_type {};
Then you could set up your class definition in the following way:
template<class T, std::size_t rows_t, std::size_t cols_t>
class test{
std::array<std::array<T, cols_t>, rows_t> _data;
template<class type_t>
std::enable_if_t<!is2D<type_t>::value, void>
test_init(type_t const &arr) {
std::size_t j = 0;
for (const auto &num : arr) _data[0][j++] = num;
}
template<class type_t>
std::enable_if_t<is2D<type_t>::value, void>
test_init(type_t const &arr) {
std::size_t i = 0;
for(const auto &el : arr) {
std::size_t j = 0;
for (const auto &num : el) _data[i][j++] = num;
++i;
}
}
public:
auto &operator[](const std::size_t &i) { return this->_data[i]; }
auto begin() { return this->_data.begin(); }
auto end() { return this->_data.end(); }
//CONSTRUCTOR
template<class type_t> test(type_t const &arr) { test_init(arr); }
};
LIVE DEMO