diff --git a/src/mesh/ItemArray.hpp b/src/mesh/ItemArray.hpp
index ccfff9143c6d7e0f2ff9ff6eeea36533e016c7f3..78e5f8dd64c430ec8149c3e608d444897987f108 100644
--- a/src/mesh/ItemArray.hpp
+++ b/src/mesh/ItemArray.hpp
@@ -81,7 +81,7 @@ class ItemArray
   {
     Assert(this->isBuilt());
     Assert(i < this->numberOfItems());
-    return m_values.row(i);
+    return m_values[i];
   }
 
   template <typename IndexType>
diff --git a/src/mesh/SubItemArrayPerItem.hpp b/src/mesh/SubItemArrayPerItem.hpp
index 431c3f4bee962857ff0b361c56a9ab5e987fde35..d39bff6a3015b61fbbe853dbeb9970e237068068 100644
--- a/src/mesh/SubItemArrayPerItem.hpp
+++ b/src/mesh/SubItemArrayPerItem.hpp
@@ -8,6 +8,8 @@
 #include <mesh/ItemType.hpp>
 #include <utils/Array.hpp>
 #include <utils/PugsAssert.hpp>
+#include <utils/Table.hpp>
+
 #include <utils/SubArray.hpp>
 
 #include <memory>
@@ -34,9 +36,7 @@ class SubItemArrayPerItem
   ConnectivityPtr m_connectivity_ptr;
 
   typename ConnectivityMatrix::HostRowType m_host_row_map;
-  Array<DataType> m_arrays_values;
-
-  size_t m_size_of_arrays;
+  Table<DataType> m_values;
 
   // Allow const std:shared_ptr version to access our data
   friend SubItemArrayPerItem<std::add_const_t<DataType>, ItemOfItem, ConnectivitySharedPtr>;
@@ -53,56 +53,6 @@ class SubItemArrayPerItem
  public:
   using ToShared = SubItemArrayPerItem<DataType, ItemOfItem, ConnectivitySharedPtr>;
 
-  class SubView
-  {
-   public:
-    using data_type = DataType;
-
-   private:
-    PUGS_RESTRICT DataType* const m_sub_arrays;
-    const size_t m_size;
-    const size_t m_size_of_arrays;
-
-   public:
-    template <typename IndexType>
-    PUGS_FORCEINLINE SubArray<DataType>
-    operator[](IndexType i) const noexcept(NO_ASSERT)
-    {
-      static_assert(std::is_integral_v<IndexType>, "SubView is indexed by integral arrays");
-      Assert(static_cast<size_t>(i) < m_size);
-      return SubArray<DataType>(m_sub_arrays, i * m_size_of_arrays, m_size_of_arrays);
-    }
-
-    template <typename IndexType>
-    PUGS_FORCEINLINE SubArray<DataType>
-    operator[](IndexType i) noexcept(NO_ASSERT)
-    {
-      static_assert(std::is_integral_v<IndexType>, "SubView is indexed by integral arrays");
-      Assert(static_cast<size_t>(i) < m_size);
-      return SubArray<DataType>(m_sub_arrays, i * m_size_of_arrays, m_size_of_arrays);
-    }
-
-    PUGS_INLINE
-    size_t
-    size() const noexcept
-    {
-      return m_size;
-    }
-
-    SubView(const SubView&) = delete;
-
-    PUGS_INLINE
-    SubView(SubView&&) noexcept = delete;
-
-    PUGS_INLINE
-    SubView(const Array<DataType>& arrays, size_t begin, size_t end, size_t size_of_arrays) noexcept(NO_ASSERT)
-      : m_sub_arrays{&(arrays[begin * size_of_arrays])}, m_size{end - begin}, m_size_of_arrays{size_of_arrays}
-    {
-      Assert(begin <= end);
-      Assert(end <= arrays.size());
-    }
-  };
-
   friend PUGS_INLINE SubItemArrayPerItem<std::remove_const_t<DataType>, ItemOfItem, ConnectivityPtr>
   copy(SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>& source)
   {
@@ -110,8 +60,8 @@ class SubItemArrayPerItem
 
     image.m_connectivity_ptr = source.m_connectivity_ptr;
     image.m_host_row_map     = source.m_host_row_map;
-    image.m_arrays_values    = copy(source.m_arrays_values);
-    image.m_size_of_arrays   = source.m_size_of_arrays;
+    image.m_values           = copy(source.m_values);
+
     return image;
   }
 
@@ -134,34 +84,34 @@ class SubItemArrayPerItem
   }
 
   template <typename IndexType, typename SubIndexType>
-  PUGS_FORCEINLINE SubArray<DataType>
+  PUGS_FORCEINLINE Array<DataType>
   operator()(IndexType item_id, SubIndexType i) const noexcept(NO_ASSERT)
   {
     static_assert(std::is_same_v<IndexType, ItemId>, "first index must be of the correct ItemId type");
     static_assert(std::is_integral_v<SubIndexType>, "second index must be an integral type");
     Assert(this->isBuilt());
     Assert(i + m_host_row_map(size_t{item_id}) < m_host_row_map(size_t{item_id} + 1));
-    return SubArray(m_arrays_values, (m_host_row_map(size_t{item_id}) + i) * m_size_of_arrays, m_size_of_arrays);
+    return m_values[m_host_row_map(size_t{item_id}) + i];
   }
 
   // Following Kokkos logic, these classes are view and const view does allow
   // changes in data
   template <typename ArrayIndexType>
-  DataType&
+  Array<DataType>
   operator[](const ArrayIndexType& i) const noexcept(NO_ASSERT)
   {
     static_assert(std::is_integral_v<ArrayIndexType>, "index must be an integral type");
     Assert(this->isBuilt());
-    Assert(static_cast<size_t>(i) < m_arrays_values.size());
-    return m_arrays_values[i];
+    Assert(static_cast<size_t>(i) < m_values.nbRows());
+    return m_values[i];
   }
 
   PUGS_INLINE
   size_t
-  numberOfValues() const noexcept(NO_ASSERT)
+  numberOfArrays() const noexcept(NO_ASSERT)
   {
     Assert(this->isBuilt());
-    return m_arrays_values.size();
+    return m_values.nbRows();
   }
 
   PUGS_INLINE
@@ -177,7 +127,7 @@ class SubItemArrayPerItem
   sizeOfArrays() const noexcept(NO_ASSERT)
   {
     Assert(this->isBuilt());
-    return m_size_of_arrays;
+    return m_values.nbColumns();
   }
 
   template <typename IndexType>
@@ -190,30 +140,38 @@ class SubItemArrayPerItem
     return m_host_row_map(size_t{item_id} + 1) - m_host_row_map(size_t{item_id});
   }
 
+  PUGS_INLINE
+  void
+  fill(const DataType& data) const noexcept
+  {
+    static_assert(not std::is_const_v<DataType>, "Cannot modify ItemValue of const");
+    m_values.fill(data);
+  }
+
   template <typename IndexType>
-  PUGS_INLINE SubView
-  itemArrays(IndexType item_id) noexcept(NO_ASSERT)
+  PUGS_INLINE Table<DataType>
+  itemTable(IndexType item_id) noexcept(NO_ASSERT)
   {
     static_assert(std::is_same_v<IndexType, ItemId>, "index must be an ItemId");
     Assert(this->isBuilt());
     Assert(item_id < this->numberOfItems());
     const auto& item_begin = m_host_row_map(size_t{item_id});
     const auto& item_end   = m_host_row_map(size_t{item_id} + 1);
-    return SubView(m_arrays_values, item_begin, item_end, m_size_of_arrays);
+    return subTable(m_values, item_begin, item_end - item_begin, 0, this->sizeOfArrays());
   }
 
   // Following Kokkos logic, these classes are view and const view does allow
   // changes in data
   template <typename IndexType>
-  PUGS_INLINE SubView
-  itemArrays(IndexType item_id) const noexcept(NO_ASSERT)
+  PUGS_INLINE Table<DataType>
+  itemTable(IndexType item_id) const noexcept(NO_ASSERT)
   {
     static_assert(std::is_same_v<IndexType, ItemId>, "index must be an ItemId");
     Assert(this->isBuilt());
     Assert(item_id < this->numberOfItems());
     const auto& item_begin = m_host_row_map(size_t{item_id});
     const auto& item_end   = m_host_row_map(size_t{item_id} + 1);
-    return SubView(m_arrays_values, item_begin, item_end, m_size_of_arrays);
+    return subTable(m_values, item_begin, item_end - item_begin, 0, this->sizeOfArrays());
   }
 
   template <typename DataType2, typename ConnectivityPtr2>
@@ -227,9 +185,8 @@ class SubItemArrayPerItem
     static_assert(((std::is_const_v<DataType2> and std::is_const_v<DataType>) or not std::is_const_v<DataType2>),
                   "Cannot assign SubItemArrayPerItem of const data to "
                   "SubItemArrayPerItem of non-const data");
-    m_host_row_map   = sub_item_array_per_item.m_host_row_map;
-    m_arrays_values  = sub_item_array_per_item.m_arrays_values;
-    m_size_of_arrays = sub_item_array_per_item.m_size_of_arrays;
+    m_host_row_map = sub_item_array_per_item.m_host_row_map;
+    m_values       = sub_item_array_per_item.m_values;
 
     if constexpr (std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr> and
                   std::is_same_v<ConnectivityPtr2, ConnectivityWeakPtr>) {
@@ -251,16 +208,16 @@ class SubItemArrayPerItem
 
   SubItemArrayPerItem() = default;
 
-  SubItemArrayPerItem(const IConnectivity& connectivity, size_t size_of_array) noexcept
-    : m_connectivity_ptr{connectivity.shared_ptr()}, m_size_of_arrays(size_of_array)
+  SubItemArrayPerItem(const IConnectivity& connectivity, size_t size_of_arrays) noexcept
+    : m_connectivity_ptr{connectivity.shared_ptr()}
   {
     static_assert(not std::is_const_v<DataType>, "Cannot allocate SubItemArrayPerItem of const data: only "
                                                  "view is supported");
 
     ConnectivityMatrix connectivity_matrix = connectivity._getMatrix(item_type, sub_item_type);
 
-    m_host_row_map  = connectivity_matrix.rowsMap();
-    m_arrays_values = Array<std::remove_const_t<DataType>>(connectivity_matrix.numEntries() * m_size_of_arrays);
+    m_host_row_map = connectivity_matrix.rowsMap();
+    m_values       = Table<std::remove_const_t<DataType>>(connectivity_matrix.numEntries(), size_of_arrays);
   }
 
   ~SubItemArrayPerItem() = default;
diff --git a/src/utils/Table.hpp b/src/utils/Table.hpp
index edb0712afe00ec962415db1a4ec4235f33672711..67f544b997f32086131a549237de091e75ba60c1 100644
--- a/src/utils/Table.hpp
+++ b/src/utils/Table.hpp
@@ -1,14 +1,12 @@
 #ifndef TABLE_HPP
 #define TABLE_HPP
 
+#include <Kokkos_CopyViews.hpp>
+#include <utils/Array.hpp>
+#include <utils/PugsAssert.hpp>
 #include <utils/PugsMacros.hpp>
 #include <utils/PugsUtils.hpp>
 
-#include <utils/PugsAssert.hpp>
-
-#include <Kokkos_CopyViews.hpp>
-#include <algorithm>
-
 template <typename DataType>
 class [[nodiscard]] Table
 {
@@ -33,14 +31,11 @@ class [[nodiscard]] Table
     return m_values.extent(1);
   }
 
-  Array<DataType> row(index_type i) const
-  {
-    return encapsulate(Kokkos::View<DataType*>(m_values, i, Kokkos::ALL()));
-  }
-
-  Array<DataType> column(index_type j) const
+  PUGS_INLINE
+  Array<DataType> operator[](index_type i) const
   {
-    return encapsulate(Kokkos::View<DataType*>(m_values, Kokkos::ALL(), j));
+    Assert(i < this->nbRows());
+    return encapsulate(Kokkos::View<DataType*>(m_values, i, Kokkos::ALL));
   }
 
   friend PUGS_INLINE Table<std::remove_const_t<DataType>> copy(const Table<DataType>& source)
@@ -54,6 +49,13 @@ class [[nodiscard]] Table
   template <typename DataType2, typename... RT>
   friend PUGS_INLINE Table<DataType2> encapsulate(const Kokkos::View<DataType2**, RT...>& values);
 
+  template <typename DataType2>
+  friend PUGS_INLINE Table<DataType2> subTable(const Table<DataType2>& table,
+                                               typename Table<DataType2>::index_type row_begin,
+                                               typename Table<DataType2>::index_type row_size,
+                                               typename Table<DataType2>::index_type column_begin,
+                                               typename Table<DataType2>::index_type column_size);
+
   PUGS_INLINE DataType& operator()(index_type i, index_type j) const noexcept(NO_ASSERT)
   {
     Assert(i < this->nbRows());
@@ -123,4 +125,20 @@ encapsulate(const Kokkos::View<DataType**, RT...>& values)
   return table;
 }
 
+template <typename DataType>
+PUGS_INLINE Table<DataType>
+subTable(const Table<DataType>& table,
+         typename Table<DataType>::index_type row_begin,
+         typename Table<DataType>::index_type row_size,
+         typename Table<DataType>::index_type column_begin,
+         typename Table<DataType>::index_type column_size)
+{
+  Assert(row_begin < table.nbRows());
+  Assert(row_begin + row_size <= table.nbRows());
+  Assert(column_begin < table.nbColumns());
+  Assert(column_begin + column_size <= table.nbColumns());
+  return encapsulate(Kokkos::View<DataType**>(table.m_values, std::make_pair(row_begin, row_begin + row_size),
+                                              std::make_pair(column_begin, column_begin + column_size)));
+}
+
 #endif   // TABLE_HPP
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index c99154ff0dee78cf7aadf95c70cccfbeb36ffa75..c36830918d3d769870250d212ea6d04e5d8316fc 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -85,8 +85,8 @@ add_executable (unit_tests
   test_PugsUtils.cpp
   test_RevisionInfo.cpp
   test_SparseMatrixDescriptor.cpp
-  test_SubArray.cpp
   test_SymbolTable.cpp
+  test_Table.cpp
   test_Timer.cpp
   test_TinyMatrix.cpp
   test_TinyVector.cpp
diff --git a/tests/test_SubItemArrayPerItem.cpp b/tests/test_SubItemArrayPerItem.cpp
index b453b9e79b430dc6b7496c1a9e533896a3df0ac1..7636ad420dc87ddf029d21b154ea0067a4eb5684 100644
--- a/tests/test_SubItemArrayPerItem.cpp
+++ b/tests/test_SubItemArrayPerItem.cpp
@@ -51,15 +51,13 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
   SECTION("dimensions")
   {
-    auto number_of_values = [](const auto& sub_item_array_per_item) -> size_t {
+    auto number_of_arrays = [](const auto& sub_item_array_per_item) -> size_t {
       using SubItemArrayPerItemType = std::decay_t<decltype(sub_item_array_per_item)>;
       using ItemId                  = typename SubItemArrayPerItemType::ItemId;
 
       size_t number = 0;
       for (ItemId item_id = 0; item_id < sub_item_array_per_item.numberOfItems(); ++item_id) {
-        for (size_t i = 0; i < sub_item_array_per_item.numberOfSubArrays(item_id); ++i) {
-          number += sub_item_array_per_item(item_id, i).size();
-        }
+        number += sub_item_array_per_item.numberOfSubArrays(item_id);
       }
       return number;
     };
@@ -73,7 +71,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
       {
         NodeArrayPerCell<int> node_array_per_cell{connectivity, 3};
         REQUIRE(node_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(node_array_per_cell.numberOfValues() == number_of_values(node_array_per_cell));
+        REQUIRE(node_array_per_cell.numberOfArrays() == number_of_arrays(node_array_per_cell));
         REQUIRE(node_array_per_cell.sizeOfArrays() == 3);
 
         auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
@@ -82,7 +80,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
           for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
             is_correct &=
               (cell_to_node_matrix[cell_id].size() == node_array_per_cell.numberOfSubArrays(cell_id)) and
-              (node_array_per_cell.itemArrays(cell_id).size() == node_array_per_cell.numberOfSubArrays(cell_id));
+              (node_array_per_cell.itemTable(cell_id).nbRows() == node_array_per_cell.numberOfSubArrays(cell_id));
           }
           REQUIRE(is_correct);
         }
@@ -92,14 +90,14 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
           bool is_correct = true;
           for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
             is_correct &=
-              (const_node_array_per_cell.itemArrays(cell_id).size() == node_array_per_cell.numberOfSubArrays(cell_id));
+              (const_node_array_per_cell.itemTable(cell_id).nbRows() == node_array_per_cell.numberOfSubArrays(cell_id));
           }
           REQUIRE(is_correct);
         }
 
         EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 4};
         REQUIRE(edge_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(edge_array_per_cell.numberOfValues() == number_of_values(edge_array_per_cell));
+        REQUIRE(edge_array_per_cell.numberOfArrays() == number_of_arrays(edge_array_per_cell));
         REQUIRE(edge_array_per_cell.sizeOfArrays() == 4);
 
         auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
@@ -113,7 +111,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
         FaceArrayPerCell<int> face_array_per_cell{connectivity, 2};
         REQUIRE(face_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(face_array_per_cell.numberOfValues() == number_of_values(face_array_per_cell));
+        REQUIRE(face_array_per_cell.numberOfArrays() == number_of_arrays(face_array_per_cell));
         REQUIRE(face_array_per_cell.sizeOfArrays() == 2);
 
         auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
@@ -130,7 +128,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
       {
         CellArrayPerFace<int> cell_array_per_face{connectivity, 2};
         REQUIRE(cell_array_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(cell_array_per_face.numberOfValues() == number_of_values(cell_array_per_face));
+        REQUIRE(cell_array_per_face.numberOfArrays() == number_of_arrays(cell_array_per_face));
         REQUIRE(cell_array_per_face.sizeOfArrays() == 2);
 
         auto face_to_cell_matrix = connectivity.faceToCellMatrix();
@@ -147,7 +145,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
       {
         CellArrayPerEdge<int> cell_array_per_edge{connectivity, 3};
         REQUIRE(cell_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(cell_array_per_edge.numberOfValues() == number_of_values(cell_array_per_edge));
+        REQUIRE(cell_array_per_edge.numberOfArrays() == number_of_arrays(cell_array_per_edge));
         REQUIRE(cell_array_per_edge.sizeOfArrays() == 3);
 
         auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
@@ -164,7 +162,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
       {
         CellArrayPerNode<int> cell_array_per_node{connectivity, 4};
         REQUIRE(cell_array_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(cell_array_per_node.numberOfValues() == number_of_values(cell_array_per_node));
+        REQUIRE(cell_array_per_node.numberOfArrays() == number_of_arrays(cell_array_per_node));
         REQUIRE(cell_array_per_node.sizeOfArrays() == 4);
 
         auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
@@ -187,7 +185,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
       {
         NodeArrayPerCell<int> node_array_per_cell{connectivity, 5};
         REQUIRE(node_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(node_array_per_cell.numberOfValues() == number_of_values(node_array_per_cell));
+        REQUIRE(node_array_per_cell.numberOfArrays() == number_of_arrays(node_array_per_cell));
         REQUIRE(node_array_per_cell.sizeOfArrays() == 5);
 
         auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
@@ -201,7 +199,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
         EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 4};
         REQUIRE(edge_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(edge_array_per_cell.numberOfValues() == number_of_values(edge_array_per_cell));
+        REQUIRE(edge_array_per_cell.numberOfArrays() == number_of_arrays(edge_array_per_cell));
         REQUIRE(edge_array_per_cell.sizeOfArrays() == 4);
 
         auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
@@ -215,7 +213,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
         FaceArrayPerCell<int> face_array_per_cell{connectivity, 3};
         REQUIRE(face_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(face_array_per_cell.numberOfValues() == number_of_values(face_array_per_cell));
+        REQUIRE(face_array_per_cell.numberOfArrays() == number_of_arrays(face_array_per_cell));
         REQUIRE(face_array_per_cell.sizeOfArrays() == 3);
 
         auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
@@ -232,7 +230,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
       {
         CellArrayPerFace<int> cell_array_per_face{connectivity, 3};
         REQUIRE(cell_array_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(cell_array_per_face.numberOfValues() == number_of_values(cell_array_per_face));
+        REQUIRE(cell_array_per_face.numberOfArrays() == number_of_arrays(cell_array_per_face));
         REQUIRE(cell_array_per_face.sizeOfArrays() == 3);
 
         auto face_to_cell_matrix = connectivity.faceToCellMatrix();
@@ -246,7 +244,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
         NodeArrayPerFace<int> node_array_per_face{connectivity, 2};
         REQUIRE(node_array_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(node_array_per_face.numberOfValues() == number_of_values(node_array_per_face));
+        REQUIRE(node_array_per_face.numberOfArrays() == number_of_arrays(node_array_per_face));
         REQUIRE(node_array_per_face.sizeOfArrays() == 2);
 
         auto face_to_node_matrix = connectivity.faceToNodeMatrix();
@@ -263,7 +261,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
       {
         CellArrayPerEdge<int> cell_array_per_edge{connectivity, 3};
         REQUIRE(cell_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(cell_array_per_edge.numberOfValues() == number_of_values(cell_array_per_edge));
+        REQUIRE(cell_array_per_edge.numberOfArrays() == number_of_arrays(cell_array_per_edge));
         REQUIRE(cell_array_per_edge.sizeOfArrays() == 3);
 
         auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
@@ -277,7 +275,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
         NodeArrayPerEdge<int> node_array_per_edge{connectivity, 5};
         REQUIRE(node_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(node_array_per_edge.numberOfValues() == number_of_values(node_array_per_edge));
+        REQUIRE(node_array_per_edge.numberOfArrays() == number_of_arrays(node_array_per_edge));
         REQUIRE(node_array_per_edge.sizeOfArrays() == 5);
 
         auto edge_to_node_matrix = connectivity.edgeToNodeMatrix();
@@ -294,7 +292,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
       {
         EdgeArrayPerNode<int> edge_array_per_node{connectivity, 4};
         REQUIRE(edge_array_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(edge_array_per_node.numberOfValues() == number_of_values(edge_array_per_node));
+        REQUIRE(edge_array_per_node.numberOfArrays() == number_of_arrays(edge_array_per_node));
         REQUIRE(edge_array_per_node.sizeOfArrays() == 4);
 
         auto node_to_edge_matrix = connectivity.nodeToEdgeMatrix();
@@ -308,7 +306,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
         FaceArrayPerNode<int> face_array_per_node{connectivity, 3};
         REQUIRE(face_array_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(face_array_per_node.numberOfValues() == number_of_values(face_array_per_node));
+        REQUIRE(face_array_per_node.numberOfArrays() == number_of_arrays(face_array_per_node));
         REQUIRE(face_array_per_node.sizeOfArrays() == 3);
 
         auto node_to_face_matrix = connectivity.nodeToFaceMatrix();
@@ -322,7 +320,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
         CellArrayPerNode<int> cell_array_per_node{connectivity, 2};
         REQUIRE(cell_array_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(cell_array_per_node.numberOfValues() == number_of_values(cell_array_per_node));
+        REQUIRE(cell_array_per_node.numberOfArrays() == number_of_arrays(cell_array_per_node));
         REQUIRE(cell_array_per_node.sizeOfArrays() == 2);
 
         auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
@@ -344,7 +342,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
       {
         NodeArrayPerCell<int> node_array_per_cell{connectivity, 3};
         REQUIRE(node_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(node_array_per_cell.numberOfValues() == number_of_values(node_array_per_cell));
+        REQUIRE(node_array_per_cell.numberOfArrays() == number_of_arrays(node_array_per_cell));
         REQUIRE(node_array_per_cell.sizeOfArrays() == 3);
 
         auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
@@ -358,7 +356,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
         EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 4};
         REQUIRE(edge_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(edge_array_per_cell.numberOfValues() == number_of_values(edge_array_per_cell));
+        REQUIRE(edge_array_per_cell.numberOfArrays() == number_of_arrays(edge_array_per_cell));
         REQUIRE(edge_array_per_cell.sizeOfArrays() == 4);
 
         auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
@@ -372,7 +370,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
         FaceArrayPerCell<int> face_array_per_cell{connectivity, 3};
         REQUIRE(face_array_per_cell.numberOfItems() == connectivity.numberOfCells());
-        REQUIRE(face_array_per_cell.numberOfValues() == number_of_values(face_array_per_cell));
+        REQUIRE(face_array_per_cell.numberOfArrays() == number_of_arrays(face_array_per_cell));
         REQUIRE(face_array_per_cell.sizeOfArrays() == 3);
 
         auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
@@ -389,7 +387,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
       {
         CellArrayPerFace<int> cell_array_per_face{connectivity, 5};
         REQUIRE(cell_array_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(cell_array_per_face.numberOfValues() == number_of_values(cell_array_per_face));
+        REQUIRE(cell_array_per_face.numberOfArrays() == number_of_arrays(cell_array_per_face));
         REQUIRE(cell_array_per_face.sizeOfArrays() == 5);
 
         auto face_to_cell_matrix = connectivity.faceToCellMatrix();
@@ -403,7 +401,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
         EdgeArrayPerFace<int> edge_array_per_face{connectivity, 3};
         REQUIRE(edge_array_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(edge_array_per_face.numberOfValues() == number_of_values(edge_array_per_face));
+        REQUIRE(edge_array_per_face.numberOfArrays() == number_of_arrays(edge_array_per_face));
         REQUIRE(edge_array_per_face.sizeOfArrays() == 3);
 
         auto face_to_edge_matrix = connectivity.faceToEdgeMatrix();
@@ -417,7 +415,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
         NodeArrayPerFace<int> node_array_per_face{connectivity, 2};
         REQUIRE(node_array_per_face.numberOfItems() == connectivity.numberOfFaces());
-        REQUIRE(node_array_per_face.numberOfValues() == number_of_values(node_array_per_face));
+        REQUIRE(node_array_per_face.numberOfArrays() == number_of_arrays(node_array_per_face));
         REQUIRE(node_array_per_face.sizeOfArrays() == 2);
 
         auto face_to_node_matrix = connectivity.faceToNodeMatrix();
@@ -434,7 +432,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
       {
         CellArrayPerEdge<int> cell_array_per_edge{connectivity, 3};
         REQUIRE(cell_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(cell_array_per_edge.numberOfValues() == number_of_values(cell_array_per_edge));
+        REQUIRE(cell_array_per_edge.numberOfArrays() == number_of_arrays(cell_array_per_edge));
         REQUIRE(cell_array_per_edge.sizeOfArrays() == 3);
 
         auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
@@ -448,7 +446,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
         FaceArrayPerEdge<int> face_array_per_edge{connectivity, 5};
         REQUIRE(face_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(face_array_per_edge.numberOfValues() == number_of_values(face_array_per_edge));
+        REQUIRE(face_array_per_edge.numberOfArrays() == number_of_arrays(face_array_per_edge));
         REQUIRE(face_array_per_edge.sizeOfArrays() == 5);
 
         auto edge_to_face_matrix = connectivity.edgeToFaceMatrix();
@@ -462,7 +460,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
         NodeArrayPerEdge<int> node_array_per_edge{connectivity, 3};
         REQUIRE(node_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
-        REQUIRE(node_array_per_edge.numberOfValues() == number_of_values(node_array_per_edge));
+        REQUIRE(node_array_per_edge.numberOfArrays() == number_of_arrays(node_array_per_edge));
         REQUIRE(node_array_per_edge.sizeOfArrays() == 3);
 
         auto edge_to_node_matrix = connectivity.edgeToNodeMatrix();
@@ -479,7 +477,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
       {
         EdgeArrayPerNode<int> edge_array_per_node{connectivity, 3};
         REQUIRE(edge_array_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(edge_array_per_node.numberOfValues() == number_of_values(edge_array_per_node));
+        REQUIRE(edge_array_per_node.numberOfArrays() == number_of_arrays(edge_array_per_node));
         REQUIRE(edge_array_per_node.sizeOfArrays() == 3);
 
         auto node_to_edge_matrix = connectivity.nodeToEdgeMatrix();
@@ -493,7 +491,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
         FaceArrayPerNode<int> face_array_per_node{connectivity, 4};
         REQUIRE(face_array_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(face_array_per_node.numberOfValues() == number_of_values(face_array_per_node));
+        REQUIRE(face_array_per_node.numberOfArrays() == number_of_arrays(face_array_per_node));
         REQUIRE(face_array_per_node.sizeOfArrays() == 4);
 
         auto node_to_face_matrix = connectivity.nodeToFaceMatrix();
@@ -507,7 +505,7 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
         CellArrayPerNode<int> cell_array_per_node{connectivity, 5};
         REQUIRE(cell_array_per_node.numberOfItems() == connectivity.numberOfNodes());
-        REQUIRE(cell_array_per_node.numberOfValues() == number_of_values(cell_array_per_node));
+        REQUIRE(cell_array_per_node.numberOfArrays() == number_of_arrays(cell_array_per_node));
         REQUIRE(cell_array_per_node.sizeOfArrays() == 5);
 
         auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
@@ -542,14 +540,22 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
       }
       {
         bool is_same = true;
-        for (size_t i = 0; i < edge_arrays_per_cell.numberOfValues(); ++i) {
-          is_same &= (edge_arrays_per_cell[i] == i);
+        size_t k     = 0;
+        for (size_t i = 0; i < edge_arrays_per_cell.numberOfArrays(); ++i) {
+          for (size_t j = 0; j < edge_arrays_per_cell.sizeOfArrays(); ++j, ++k) {
+            is_same &= (edge_arrays_per_cell[i][j] == k);
+          }
         }
         REQUIRE(is_same);
       }
 
-      for (size_t i = 0; i < edge_arrays_per_cell.numberOfValues(); ++i) {
-        edge_arrays_per_cell[i] = i * i + 1;
+      {
+        size_t k = 0;
+        for (size_t i = 0; i < edge_arrays_per_cell.numberOfArrays(); ++i) {
+          for (size_t j = 0; j < edge_arrays_per_cell.sizeOfArrays(); ++j, ++k) {
+            edge_arrays_per_cell[i][j] = k * k + 1;
+          }
+        }
       }
       {
         bool is_same = true;
@@ -569,9 +575,9 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
         bool is_same = true;
         size_t i     = 0;
         for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-          const auto& edge_arrays = const_edge_arrays_per_cell.itemArrays(cell_id);
-          for (size_t i_edge = 0; i_edge < edge_arrays.size(); ++i_edge) {
-            const auto& array = edge_arrays[i_edge];
+          const auto& cell_table = const_edge_arrays_per_cell.itemTable(cell_id);
+          for (size_t i_edge = 0; i_edge < cell_table.nbRows(); ++i_edge) {
+            const auto& array = cell_table[i_edge];
             for (size_t l = 0; l < array.size(); ++l, ++i) {
               is_same &= (array[l] == i * i + 1);
             }
@@ -599,13 +605,21 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
       }
       {
         bool is_same = true;
-        for (size_t i = 0; i < cell_arrays_per_face.numberOfValues(); ++i) {
-          is_same &= (cell_arrays_per_face[i] == i);
+        size_t k     = 0;
+        for (size_t i = 0; i < cell_arrays_per_face.numberOfArrays(); ++i) {
+          for (size_t j = 0; j < cell_arrays_per_face.sizeOfArrays(); ++j, ++k) {
+            is_same &= (cell_arrays_per_face[i][j] == k);
+          }
         }
         REQUIRE(is_same);
       }
-      for (size_t i = 0; i < cell_arrays_per_face.numberOfValues(); ++i) {
-        cell_arrays_per_face[i] = 3 * i + 1;
+      {
+        size_t k = 0;
+        for (size_t i = 0; i < cell_arrays_per_face.numberOfArrays(); ++i) {
+          for (size_t j = 0; j < cell_arrays_per_face.sizeOfArrays(); ++j, ++k) {
+            cell_arrays_per_face[i][j] = 3 * k + 1;
+          }
+        }
       }
       {
         bool is_same = true;
@@ -625,9 +639,9 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
         bool is_same = true;
         size_t i     = 0;
         for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
-          const auto& cell_arrays = const_cell_arrays_per_face.itemArrays(face_id);
-          for (size_t i_cell = 0; i_cell < cell_arrays.size(); ++i_cell) {
-            const auto& array = cell_arrays[i_cell];
+          const auto& face_table = const_cell_arrays_per_face.itemTable(face_id);
+          for (size_t i_cell = 0; i_cell < face_table.nbRows(); ++i_cell) {
+            const auto& array = face_table[i_cell];
             for (size_t l = 0; l < array.size(); ++l, ++i) {
               is_same &= (array[l] == 3 * i + 1);
             }
@@ -654,14 +668,21 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
       }
       {
         bool is_same = true;
-        for (size_t i = 0; i < face_arrays_per_node.numberOfValues(); ++i) {
-          is_same &= (face_arrays_per_node[i] == i);
+        size_t k     = 0;
+        for (size_t i = 0; i < face_arrays_per_node.numberOfArrays(); ++i) {
+          for (size_t j = 0; j < face_arrays_per_node.sizeOfArrays(); ++j, ++k) {
+            is_same &= (face_arrays_per_node[i][j] == k);
+          }
+          REQUIRE(is_same);
         }
-        REQUIRE(is_same);
       }
-
-      for (size_t i = 0; i < face_arrays_per_node.numberOfValues(); ++i) {
-        face_arrays_per_node[i] = 3 + i * i;
+      {
+        size_t k = 0;
+        for (size_t i = 0; i < face_arrays_per_node.numberOfArrays(); ++i) {
+          for (size_t j = 0; j < face_arrays_per_node.sizeOfArrays(); ++j, ++k) {
+            face_arrays_per_node[i][j] = 3 + k * k;
+          }
+        }
       }
       {
         bool is_same = true;
@@ -681,9 +702,9 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
         bool is_same = true;
         size_t i     = 0;
         for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
-          const auto& face_arrays = const_face_arrays_per_node.itemArrays(node_id);
-          for (size_t i_face = 0; i_face < face_arrays.size(); ++i_face) {
-            const auto& array = face_arrays[i_face];
+          const auto& node_table = const_face_arrays_per_node.itemTable(node_id);
+          for (size_t i_face = 0; i_face < node_table.nbRows(); ++i_face) {
+            const auto& array = node_table[i_face];
             for (size_t l = 0; l < array.size(); ++l, ++i) {
               is_same &= (array[l] == 3 + i * i);
             }
@@ -702,15 +723,21 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
     SECTION("classic")
     {
       NodeArrayPerCell<size_t> node_array_per_cell{connectivity, 3};
-      for (size_t i = 0; i < node_array_per_cell.numberOfValues(); ++i) {
-        node_array_per_cell[i] = i;
+      {
+        size_t k = 0;
+        for (size_t i = 0; i < node_array_per_cell.numberOfArrays(); ++i) {
+          for (size_t j = 0; j < node_array_per_cell.sizeOfArrays(); ++j, ++k) {
+            node_array_per_cell[i][j] = k;
+          }
+        }
       }
-
       NodeArrayPerCell<size_t> copy_node_array_per_cell = copy(node_array_per_cell);
       {
         bool is_same = true;
-        for (size_t i = 0; i < copy_node_array_per_cell.numberOfValues(); ++i) {
-          is_same &= (copy_node_array_per_cell[i] == node_array_per_cell[i]);
+        for (size_t i = 0; i < copy_node_array_per_cell.numberOfArrays(); ++i) {
+          for (size_t j = 0; j < node_array_per_cell.sizeOfArrays(); ++j) {
+            is_same &= (copy_node_array_per_cell[i][j] == node_array_per_cell[i][j]);
+          }
         }
 
         REQUIRE(is_same);
@@ -718,9 +745,9 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
       {
         for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-          auto node_array_list = node_array_per_cell.itemArrays(cell_id);
+          auto cell_table = node_array_per_cell.itemTable(cell_id);
           for (size_t i_node = 0; i_node < node_array_per_cell.numberOfSubArrays(cell_id); ++i_node) {
-            auto node_array = node_array_list[i_node];
+            auto node_array = cell_table[i_node];
             for (size_t i = 0; i < node_array.size(); ++i) {
               node_array[i] = cell_id + i_node + i;
             }
@@ -730,8 +757,10 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
       {
         bool is_same = true;
-        for (size_t i = 0; i < copy_node_array_per_cell.numberOfValues(); ++i) {
-          is_same &= (copy_node_array_per_cell[i] == node_array_per_cell[i]);
+        for (size_t i = 0; i < copy_node_array_per_cell.numberOfArrays(); ++i) {
+          for (size_t j = 0; j < copy_node_array_per_cell.sizeOfArrays(); ++j) {
+            is_same &= (copy_node_array_per_cell[i][j] == node_array_per_cell[i][j]);
+          }
         }
 
         REQUIRE(not is_same);
@@ -741,15 +770,22 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
     SECTION("from weak")
     {
       WeakNodeArrayPerCell<size_t> node_array_per_cell{connectivity, 3};
-      for (size_t i = 0; i < node_array_per_cell.numberOfValues(); ++i) {
-        node_array_per_cell[i] = i;
+      {
+        size_t k = 0;
+        for (size_t i = 0; i < node_array_per_cell.numberOfArrays(); ++i) {
+          for (size_t j = 0; j < node_array_per_cell.sizeOfArrays(); ++j, ++k) {
+            node_array_per_cell[i][j] = k;
+          }
+        }
       }
 
       NodeArrayPerCell<size_t> copy_node_array_per_cell = copy(node_array_per_cell);
       {
         bool is_same = true;
-        for (size_t i = 0; i < copy_node_array_per_cell.numberOfValues(); ++i) {
-          is_same &= (copy_node_array_per_cell[i] == node_array_per_cell[i]);
+        for (size_t i = 0; i < copy_node_array_per_cell.numberOfArrays(); ++i) {
+          for (size_t j = 0; j < node_array_per_cell.sizeOfArrays(); ++j) {
+            is_same &= (copy_node_array_per_cell[i][j] == node_array_per_cell[i][j]);
+          }
         }
 
         REQUIRE(is_same);
@@ -757,9 +793,9 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
       {
         for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-          auto node_array_list = node_array_per_cell.itemArrays(cell_id);
+          auto cell_table = node_array_per_cell.itemTable(cell_id);
           for (size_t i_node = 0; i_node < node_array_per_cell.numberOfSubArrays(cell_id); ++i_node) {
-            auto node_array = node_array_list[i_node];
+            auto node_array = cell_table[i_node];
             for (size_t i = 0; i < node_array.size(); ++i) {
               node_array[i] = cell_id + i_node + i;
             }
@@ -769,8 +805,10 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
 
       {
         bool is_same = true;
-        for (size_t i = 0; i < copy_node_array_per_cell.numberOfValues(); ++i) {
-          is_same &= (copy_node_array_per_cell[i] == node_array_per_cell[i]);
+        for (size_t i = 0; i < copy_node_array_per_cell.numberOfArrays(); ++i) {
+          for (size_t j = 0; j < node_array_per_cell.sizeOfArrays(); ++j) {
+            is_same &= (copy_node_array_per_cell[i][j] == node_array_per_cell[i][j]);
+          }
         }
 
         REQUIRE(not is_same);
@@ -784,9 +822,13 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
     const Connectivity<2>& connectivity  = mesh_2d.connectivity();
 
     WeakFaceArrayPerCell<int> weak_face_array_per_cell{connectivity, 3};
-
-    for (size_t i = 0; i < weak_face_array_per_cell.numberOfValues(); ++i) {
-      weak_face_array_per_cell[i] = i;
+    {
+      size_t k = 0;
+      for (size_t i = 0; i < weak_face_array_per_cell.numberOfArrays(); ++i) {
+        for (size_t j = 0; j < weak_face_array_per_cell.sizeOfArrays(); ++j, ++k) {
+          weak_face_array_per_cell[i][j] = k;
+        }
+      }
     }
 
     FaceArrayPerCell<const int> face_array_per_cell{weak_face_array_per_cell};
@@ -795,8 +837,10 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
     REQUIRE(face_array_per_cell.sizeOfArrays() == weak_face_array_per_cell.sizeOfArrays());
 
     bool is_same = true;
-    for (size_t i = 0; i < weak_face_array_per_cell.numberOfValues(); ++i) {
-      is_same &= (face_array_per_cell[i] == weak_face_array_per_cell[i]);
+    for (size_t i = 0; i < weak_face_array_per_cell.numberOfArrays(); ++i) {
+      for (size_t j = 0; j < weak_face_array_per_cell.sizeOfArrays(); ++j) {
+        is_same &= (face_array_per_cell[i][j] == weak_face_array_per_cell[i][j]);
+      }
     }
     REQUIRE(is_same);
   }
@@ -808,37 +852,37 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
     {
       CellArrayPerNode<int> cell_array_per_node;
       REQUIRE_THROWS_AS(cell_array_per_node[0], AssertError);
-      REQUIRE_THROWS_AS(cell_array_per_node.itemArrays(NodeId{0}), AssertError);
+      REQUIRE_THROWS_AS(cell_array_per_node.itemTable(NodeId{0}), AssertError);
       REQUIRE_THROWS_AS(cell_array_per_node(NodeId{0}, 0), AssertError);
       REQUIRE_THROWS_AS(cell_array_per_node.sizeOfArrays(), AssertError);
-      REQUIRE_THROWS_AS(cell_array_per_node.numberOfValues(), AssertError);
+      REQUIRE_THROWS_AS(cell_array_per_node.numberOfArrays(), AssertError);
       REQUIRE_THROWS_AS(cell_array_per_node.numberOfItems(), AssertError);
       REQUIRE_THROWS_AS(cell_array_per_node.numberOfSubArrays(NodeId{0}), AssertError);
 
       FaceArrayPerCell<int> face_array_per_cell;
       REQUIRE_THROWS_AS(face_array_per_cell[0], AssertError);
-      REQUIRE_THROWS_AS(face_array_per_cell.itemArrays(CellId{0}), AssertError);
+      REQUIRE_THROWS_AS(face_array_per_cell.itemTable(CellId{0}), AssertError);
       REQUIRE_THROWS_AS(face_array_per_cell(CellId{0}, 0), AssertError);
       REQUIRE_THROWS_AS(face_array_per_cell.sizeOfArrays(), AssertError);
-      REQUIRE_THROWS_AS(face_array_per_cell.numberOfValues(), AssertError);
+      REQUIRE_THROWS_AS(face_array_per_cell.numberOfArrays(), AssertError);
       REQUIRE_THROWS_AS(face_array_per_cell.numberOfItems(), AssertError);
       REQUIRE_THROWS_AS(face_array_per_cell.numberOfSubArrays(CellId{0}), AssertError);
 
       CellArrayPerEdge<int> cell_array_per_edge;
       REQUIRE_THROWS_AS(cell_array_per_edge[0], AssertError);
-      REQUIRE_THROWS_AS(cell_array_per_edge.itemArrays(EdgeId{0}), AssertError);
+      REQUIRE_THROWS_AS(cell_array_per_edge.itemTable(EdgeId{0}), AssertError);
       REQUIRE_THROWS_AS(cell_array_per_edge(EdgeId{0}, 0), AssertError);
       REQUIRE_THROWS_AS(cell_array_per_edge.sizeOfArrays(), AssertError);
-      REQUIRE_THROWS_AS(cell_array_per_edge.numberOfValues(), AssertError);
+      REQUIRE_THROWS_AS(cell_array_per_edge.numberOfArrays(), AssertError);
       REQUIRE_THROWS_AS(cell_array_per_edge.numberOfItems(), AssertError);
       REQUIRE_THROWS_AS(cell_array_per_edge.numberOfSubArrays(EdgeId{0}), AssertError);
 
       NodeArrayPerFace<int> node_array_per_face;
       REQUIRE_THROWS_AS(node_array_per_face[0], AssertError);
-      REQUIRE_THROWS_AS(node_array_per_face.itemArrays(FaceId{0}), AssertError);
+      REQUIRE_THROWS_AS(node_array_per_face.itemTable(FaceId{0}), AssertError);
       REQUIRE_THROWS_AS(node_array_per_face(FaceId{0}, 0), AssertError);
       REQUIRE_THROWS_AS(node_array_per_face.sizeOfArrays(), AssertError);
-      REQUIRE_THROWS_AS(node_array_per_face.numberOfValues(), AssertError);
+      REQUIRE_THROWS_AS(node_array_per_face.numberOfArrays(), AssertError);
       REQUIRE_THROWS_AS(node_array_per_face.numberOfItems(), AssertError);
       REQUIRE_THROWS_AS(node_array_per_face.numberOfSubArrays(FaceId{0}), AssertError);
     }
@@ -854,13 +898,13 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
         REQUIRE_THROWS_AS(cell_array_per_face(invalid_face_id, 0), AssertError);
       }
       if (connectivity.numberOfFaces() > 0) {
-        FaceId face_id          = 0;
-        const auto& cell_arrays = cell_array_per_face.itemArrays(face_id);
-        REQUIRE_THROWS_AS(cell_array_per_face(face_id, cell_arrays.size()), AssertError);
-        REQUIRE_THROWS_AS(cell_arrays[cell_arrays.size()], AssertError);
-        REQUIRE_THROWS_AS(cell_array_per_face.itemArrays(face_id)[cell_arrays.size()], AssertError);
-        REQUIRE_THROWS_AS(cell_array_per_face.itemArrays(face_id)[0][cell_array_per_face.sizeOfArrays()], AssertError);
-        REQUIRE_THROWS_AS(cell_array_per_face.itemArrays(face_id)[0][cell_array_per_face.sizeOfArrays()] = 2,
+        FaceId face_id         = 0;
+        const auto& face_table = cell_array_per_face.itemTable(face_id);
+        REQUIRE_THROWS_AS(cell_array_per_face(face_id, face_table.nbRows()), AssertError);
+        REQUIRE_THROWS_AS(face_table[face_table.nbRows()], AssertError);
+        REQUIRE_THROWS_AS(cell_array_per_face.itemTable(face_id)[face_table.nbRows()], AssertError);
+        REQUIRE_THROWS_AS(cell_array_per_face.itemTable(face_id)[0][cell_array_per_face.sizeOfArrays()], AssertError);
+        REQUIRE_THROWS_AS(cell_array_per_face.itemTable(face_id)[0][cell_array_per_face.sizeOfArrays()] = 2,
                           AssertError);
       }
 
@@ -870,13 +914,13 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
         REQUIRE_THROWS_AS(face_array_per_node(invalid_node_id, 0), AssertError);
       }
       if (connectivity.numberOfNodes() > 0) {
-        NodeId node_id          = 0;
-        const auto& face_arrays = face_array_per_node.itemArrays(node_id);
-        REQUIRE_THROWS_AS(face_array_per_node(node_id, face_arrays.size()), AssertError);
-        REQUIRE_THROWS_AS(face_arrays[face_arrays.size()], AssertError);
-        REQUIRE_THROWS_AS(face_array_per_node.itemArrays(node_id)[face_arrays.size()], AssertError);
-        REQUIRE_THROWS_AS(face_array_per_node.itemArrays(node_id)[0][face_array_per_node.sizeOfArrays()], AssertError);
-        REQUIRE_THROWS_AS(face_array_per_node.itemArrays(node_id)[0][face_array_per_node.sizeOfArrays()] = 2,
+        NodeId node_id         = 0;
+        const auto& node_table = face_array_per_node.itemTable(node_id);
+        REQUIRE_THROWS_AS(face_array_per_node(node_id, node_table.nbRows()), AssertError);
+        REQUIRE_THROWS_AS(node_table[node_table.nbRows()], AssertError);
+        REQUIRE_THROWS_AS(face_array_per_node.itemTable(node_id)[node_table.nbRows()], AssertError);
+        REQUIRE_THROWS_AS(face_array_per_node.itemTable(node_id)[0][face_array_per_node.sizeOfArrays()], AssertError);
+        REQUIRE_THROWS_AS(face_array_per_node.itemTable(node_id)[0][face_array_per_node.sizeOfArrays()] = 2,
                           AssertError);
       }
 
@@ -886,13 +930,13 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
         REQUIRE_THROWS_AS(edge_array_per_cell(invalid_cell_id, 0), AssertError);
       }
       if (connectivity.numberOfCells() > 0) {
-        CellId cell_id          = 0;
-        const auto& edge_arrays = edge_array_per_cell.itemArrays(cell_id);
-        REQUIRE_THROWS_AS(edge_array_per_cell(cell_id, edge_arrays.size()), AssertError);
-        REQUIRE_THROWS_AS(edge_arrays[edge_arrays.size()], AssertError);
-        REQUIRE_THROWS_AS(edge_array_per_cell.itemArrays(cell_id)[edge_arrays.size()], AssertError);
-        REQUIRE_THROWS_AS(edge_array_per_cell.itemArrays(cell_id)[0][edge_array_per_cell.sizeOfArrays()], AssertError);
-        REQUIRE_THROWS_AS(edge_array_per_cell.itemArrays(cell_id)[0][edge_array_per_cell.sizeOfArrays()] == 2,
+        CellId cell_id         = 0;
+        const auto& cell_table = edge_array_per_cell.itemTable(cell_id);
+        REQUIRE_THROWS_AS(edge_array_per_cell(cell_id, cell_table.nbRows()), AssertError);
+        REQUIRE_THROWS_AS(cell_table[cell_table.nbRows()], AssertError);
+        REQUIRE_THROWS_AS(edge_array_per_cell.itemTable(cell_id)[cell_table.nbRows()], AssertError);
+        REQUIRE_THROWS_AS(edge_array_per_cell.itemTable(cell_id)[0][edge_array_per_cell.sizeOfArrays()], AssertError);
+        REQUIRE_THROWS_AS(edge_array_per_cell.itemTable(cell_id)[0][edge_array_per_cell.sizeOfArrays()] == 2,
                           AssertError);
       }
 
@@ -902,13 +946,13 @@ TEST_CASE("SubItemArrayPerItem", "[mesh]")
         REQUIRE_THROWS_AS(node_array_per_edge(invalid_edge_id, 0), AssertError);
       }
       if (connectivity.numberOfEdges() > 0) {
-        EdgeId edge_id          = 0;
-        const auto& node_arrays = node_array_per_edge.itemArrays(edge_id);
-        REQUIRE_THROWS_AS(node_array_per_edge(edge_id, node_arrays.size()), AssertError);
-        REQUIRE_THROWS_AS(node_arrays[node_arrays.size()], AssertError);
-        REQUIRE_THROWS_AS(node_array_per_edge.itemArrays(edge_id)[node_arrays.size()], AssertError);
-        REQUIRE_THROWS_AS(node_array_per_edge.itemArrays(edge_id)[0][node_array_per_edge.sizeOfArrays()], AssertError);
-        REQUIRE_THROWS_AS(node_array_per_edge.itemArrays(edge_id)[0][node_array_per_edge.sizeOfArrays()] = 2,
+        EdgeId edge_id         = 0;
+        const auto& edge_table = node_array_per_edge.itemTable(edge_id);
+        REQUIRE_THROWS_AS(node_array_per_edge(edge_id, edge_table.nbRows()), AssertError);
+        REQUIRE_THROWS_AS(edge_table[edge_table.nbRows()], AssertError);
+        REQUIRE_THROWS_AS(node_array_per_edge.itemTable(edge_id)[edge_table.nbRows()], AssertError);
+        REQUIRE_THROWS_AS(node_array_per_edge.itemTable(edge_id)[0][node_array_per_edge.sizeOfArrays()], AssertError);
+        REQUIRE_THROWS_AS(node_array_per_edge.itemTable(edge_id)[0][node_array_per_edge.sizeOfArrays()] = 2,
                           AssertError);
       }
     }
diff --git a/tests/test_Table.cpp b/tests/test_Table.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7b736f3d76f87129b45885fed737a361bf73562e
--- /dev/null
+++ b/tests/test_Table.cpp
@@ -0,0 +1,171 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/PugsAssert.hpp>
+#include <utils/Table.hpp>
+#include <utils/Types.hpp>
+
+// Instantiate to ensure full coverage is performed
+template class Table<int>;
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("Table", "[utils]")
+{
+  Table<int> a(4, 3);
+  REQUIRE(a.nbRows() == 4);
+  REQUIRE(a.nbColumns() == 3);
+  REQUIRE(a[0].size() == 3);
+  REQUIRE(a[1].size() == 3);
+  REQUIRE(a[2].size() == 3);
+  REQUIRE(a[3].size() == 3);
+
+  for (size_t i = 0; i < a.nbRows(); ++i) {
+    for (size_t j = 0; j < a.nbColumns(); ++j) {
+      a(i, j) = 2 * i + j;
+    }
+  }
+
+  REQUIRE(((a(0, 0) == 0) and (a(1, 0) == 2) and (a(2, 0) == 4) and (a(3, 0) == 6) and   //
+           (a(0, 1) == 1) and (a(1, 1) == 3) and (a(2, 1) == 5) and (a(3, 1) == 7) and   //
+           (a(0, 2) == 2) and (a(1, 2) == 4) and (a(2, 2) == 6) and (a(3, 2) == 8)));
+
+  SECTION("checking for rows")
+  {
+    REQUIRE(((a[0][0] == 0) and (a[1][0] == 2) and (a[2][0] == 4) and (a[3][0] == 6) and   //
+             (a[0][1] == 1) and (a[1][1] == 3) and (a[2][1] == 5) and (a[3][1] == 7) and   //
+             (a[0][2] == 2) and (a[1][2] == 4) and (a[2][2] == 6) and (a[3][2] == 8)));
+
+    a[2][1] = 17;
+    REQUIRE(a[2][1] == a(2, 1));
+    a[2][1] = 5;
+  }
+
+  SECTION("checking for copies")
+  {
+    Table<const int> b{a};
+
+    REQUIRE(((b(0, 0) == 0) and (b(1, 0) == 2) and (b(2, 0) == 4) and (b(3, 0) == 6) and   //
+             (b(0, 1) == 1) and (b(1, 1) == 3) and (b(2, 1) == 5) and (b(3, 1) == 7) and   //
+             (b(0, 2) == 2) and (b(1, 2) == 4) and (b(2, 2) == 6) and (b(3, 2) == 8)));
+
+    Table<int> c{a};
+
+    REQUIRE(((c(0, 0) == 0) and (c(1, 0) == 2) and (c(2, 0) == 4) and (c(3, 0) == 6) and   //
+             (c(0, 1) == 1) and (c(1, 1) == 3) and (c(2, 1) == 5) and (c(3, 1) == 7) and   //
+             (c(0, 2) == 2) and (c(1, 2) == 4) and (c(2, 2) == 6) and (c(3, 2) == 8)));
+
+    Table<int> d = std::move(c);
+
+    REQUIRE(((d(0, 0) == 0) and (d(1, 0) == 2) and (d(2, 0) == 4) and (d(3, 0) == 6) and   //
+             (d(0, 1) == 1) and (d(1, 1) == 3) and (d(2, 1) == 5) and (d(3, 1) == 7) and   //
+             (d(0, 2) == 2) and (d(1, 2) == 4) and (d(2, 2) == 6) and (d(3, 2) == 8)));
+
+    a(2, 2) = 17;
+
+    REQUIRE(a(2, 2) == 17);
+    REQUIRE(b(2, 2) == 17);
+    REQUIRE(d(2, 2) == 17);
+
+    a(2, 2) = 6;
+
+    REQUIRE(a(2, 2) == 6);
+    REQUIRE(b(2, 2) == 6);
+    REQUIRE(d(2, 2) == 6);
+  }
+
+  SECTION("checking for fill")
+  {
+    Table<int> b(3, 2);
+    b.fill(3);
+
+    REQUIRE(((b(0, 0) == 3) and (b(1, 0) == 3) and (b(2, 0) == 3) and   //
+             (b(0, 1) == 3) and (b(1, 1) == 3) and (b(2, 1) == 3)));
+  }
+
+  SECTION("checking for affectations (shallow copy)")
+  {
+    Table<const int> b;
+    b = a;
+
+    REQUIRE(((b(0, 0) == 0) and (b(1, 0) == 2) and (b(2, 0) == 4) and (b(3, 0) == 6) and   //
+             (b(0, 1) == 1) and (b(1, 1) == 3) and (b(2, 1) == 5) and (b(3, 1) == 7) and   //
+             (b(0, 2) == 2) and (b(1, 2) == 4) and (b(2, 2) == 6) and (b(3, 2) == 8)));
+
+    Table<int> c;
+    c = a;
+
+    REQUIRE(((c(0, 0) == 0) and (c(1, 0) == 2) and (c(2, 0) == 4) and (c(3, 0) == 6) and   //
+             (c(0, 1) == 1) and (c(1, 1) == 3) and (c(2, 1) == 5) and (c(3, 1) == 7) and   //
+             (c(0, 2) == 2) and (c(1, 2) == 4) and (c(2, 2) == 6) and (c(3, 2) == 8)));
+
+    Table<int> d;
+    d = std::move(c);
+
+    REQUIRE(((d(0, 0) == 0) and (d(1, 0) == 2) and (d(2, 0) == 4) and (d(3, 0) == 6) and   //
+             (d(0, 1) == 1) and (d(1, 1) == 3) and (d(2, 1) == 5) and (d(3, 1) == 7) and   //
+             (d(0, 2) == 2) and (d(1, 2) == 4) and (d(2, 2) == 6) and (d(3, 2) == 8)));
+  }
+
+  SECTION("checking for affectations (deep copy)")
+  {
+    Table<int> b(copy(a));
+
+    REQUIRE(((b(0, 0) == 0) and (b(1, 0) == 2) and (b(2, 0) == 4) and (b(3, 0) == 6) and   //
+             (b(0, 1) == 1) and (b(1, 1) == 3) and (b(2, 1) == 5) and (b(3, 1) == 7) and   //
+             (b(0, 2) == 2) and (b(1, 2) == 4) and (b(2, 2) == 6) and (b(3, 2) == 8)));
+
+    b.fill(2);
+
+    REQUIRE(((b(0, 0) == 2) and (b(1, 0) == 2) and (b(2, 0) == 2) and (b(3, 0) == 2) and   //
+             (b(0, 1) == 2) and (b(1, 1) == 2) and (b(2, 1) == 2) and (b(3, 1) == 2) and   //
+             (b(0, 2) == 2) and (b(1, 2) == 2) and (b(2, 2) == 2) and (b(3, 2) == 2)));
+
+    REQUIRE(((a(0, 0) == 0) and (a(1, 0) == 2) and (a(2, 0) == 4) and (a(3, 0) == 6) and   //
+             (a(0, 1) == 1) and (a(1, 1) == 3) and (a(2, 1) == 5) and (a(3, 1) == 7) and   //
+             (a(0, 2) == 2) and (a(1, 2) == 4) and (a(2, 2) == 6) and (a(3, 2) == 8)));
+
+    Table<int> c;
+    c = a;
+
+    REQUIRE(((c(0, 0) == 0) and (c(1, 0) == 2) and (c(2, 0) == 4) and (c(3, 0) == 6) and   //
+             (c(0, 1) == 1) and (c(1, 1) == 3) and (c(2, 1) == 5) and (c(3, 1) == 7) and   //
+             (c(0, 2) == 2) and (c(1, 2) == 4) and (c(2, 2) == 6) and (c(3, 2) == 8)));
+
+    c = copy(b);
+
+    REQUIRE(((c(0, 0) == 2) and (c(1, 0) == 2) and (c(2, 0) == 2) and (c(3, 0) == 2) and   //
+             (c(0, 1) == 2) and (c(1, 1) == 2) and (c(2, 1) == 2) and (c(3, 1) == 2) and   //
+             (c(0, 2) == 2) and (c(1, 2) == 2) and (c(2, 2) == 2) and (c(3, 2) == 2)));
+  }
+
+  SECTION("checking for Kokkos::View encaspulation")
+  {
+    {
+      Kokkos::View<double**> kokkos_view("anonymous", 10, 3);
+      for (size_t i = 0; i < 10; ++i) {
+        for (size_t j = 0; j < 3; ++j) {
+          kokkos_view(i, j) = 3 * i + j;
+        }
+      }
+
+      Table table = encapsulate(kokkos_view);
+
+      REQUIRE(table.nbRows() == kokkos_view.extent(0));
+      REQUIRE(table.nbColumns() == kokkos_view.extent(1));
+      for (size_t i = 0; i < table.nbRows(); ++i) {
+        for (size_t j = 0; j < table.nbColumns(); ++j) {
+          REQUIRE(&table(i, j) == &kokkos_view(i, j));
+        }
+      }
+    }
+  }
+
+#ifndef NDEBUG
+  SECTION("checking for bounds violation")
+  {
+    REQUIRE_THROWS_AS(a(4, 0), AssertError);
+    REQUIRE_THROWS_AS(a(0, 3), AssertError);
+  }
+#endif   // NDEBUG
+}