2D counterpart of std::array in C++17

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;








13












$begingroup$


I implemented a 2D counterpart of std::array named array2d in C++17. It is an aggregate like std::array, and provides similar interface. The goal is that if you know how to use std::array, then you will find yourself at home using array2d. Any comments are welcome :) For better viewing experience with highlighting, you can refer to this GitHub page.



#include <cstddef>
#include <array>
#include <iterator>

template <typename T, std::size_t N0, std::size_t N1>
struct array2d
using row_t = std::array<T, N1>;
inline static constexpr std::array sizes N0, N1 ;

static constexpr std::size_t size() noexcept return N0 * N1;
static constexpr bool empty() noexcept return !size();

T& at(std::size_t i, std::size_t j) return data_.at(i).at(j);
const T& at(std::size_t i, std::size_t j) const return data_.at(i).at(j);

row_t& operator(std::size_t i) noexcept return data_[i];
const row_t& operator(std::size_t i) const noexcept return data_[i];

T& front() return data_.front().front();
const T& front() const return data_.front().front();

T& back() return data_.back().back();
const T& back() const return data_.back().back();

T* data() noexcept return data_.data()->data();
const T* data() const noexcept return data_.data()->data();

T* begin() noexcept return data();
const T* begin() const noexcept return data();

T* end() noexcept return data() + size();
const T* end() const noexcept return data() + size();

auto rbegin() noexcept return std::make_reverse_iterator(end());
auto rbegin() const noexcept return std::make_reverse_iterator(end());

auto rend() noexcept return std::make_reverse_iterator(begin());
auto rend() const noexcept return std::make_reverse_iterator(begin());

void fill(const T& v)
for (auto& row : data_)
row.fill(v);



friend void swap(array2d& a, array2d& b) a.data_.swap(b.data_);

std::array<row_t, N0> data_;
;









share|improve this question









$endgroup$


















    13












    $begingroup$


    I implemented a 2D counterpart of std::array named array2d in C++17. It is an aggregate like std::array, and provides similar interface. The goal is that if you know how to use std::array, then you will find yourself at home using array2d. Any comments are welcome :) For better viewing experience with highlighting, you can refer to this GitHub page.



    #include <cstddef>
    #include <array>
    #include <iterator>

    template <typename T, std::size_t N0, std::size_t N1>
    struct array2d
    using row_t = std::array<T, N1>;
    inline static constexpr std::array sizes N0, N1 ;

    static constexpr std::size_t size() noexcept return N0 * N1;
    static constexpr bool empty() noexcept return !size();

    T& at(std::size_t i, std::size_t j) return data_.at(i).at(j);
    const T& at(std::size_t i, std::size_t j) const return data_.at(i).at(j);

    row_t& operator(std::size_t i) noexcept return data_[i];
    const row_t& operator(std::size_t i) const noexcept return data_[i];

    T& front() return data_.front().front();
    const T& front() const return data_.front().front();

    T& back() return data_.back().back();
    const T& back() const return data_.back().back();

    T* data() noexcept return data_.data()->data();
    const T* data() const noexcept return data_.data()->data();

    T* begin() noexcept return data();
    const T* begin() const noexcept return data();

    T* end() noexcept return data() + size();
    const T* end() const noexcept return data() + size();

    auto rbegin() noexcept return std::make_reverse_iterator(end());
    auto rbegin() const noexcept return std::make_reverse_iterator(end());

    auto rend() noexcept return std::make_reverse_iterator(begin());
    auto rend() const noexcept return std::make_reverse_iterator(begin());

    void fill(const T& v)
    for (auto& row : data_)
    row.fill(v);



    friend void swap(array2d& a, array2d& b) a.data_.swap(b.data_);

    std::array<row_t, N0> data_;
    ;









    share|improve this question









    $endgroup$














      13












      13








      13


      1



      $begingroup$


      I implemented a 2D counterpart of std::array named array2d in C++17. It is an aggregate like std::array, and provides similar interface. The goal is that if you know how to use std::array, then you will find yourself at home using array2d. Any comments are welcome :) For better viewing experience with highlighting, you can refer to this GitHub page.



      #include <cstddef>
      #include <array>
      #include <iterator>

      template <typename T, std::size_t N0, std::size_t N1>
      struct array2d
      using row_t = std::array<T, N1>;
      inline static constexpr std::array sizes N0, N1 ;

      static constexpr std::size_t size() noexcept return N0 * N1;
      static constexpr bool empty() noexcept return !size();

      T& at(std::size_t i, std::size_t j) return data_.at(i).at(j);
      const T& at(std::size_t i, std::size_t j) const return data_.at(i).at(j);

      row_t& operator(std::size_t i) noexcept return data_[i];
      const row_t& operator(std::size_t i) const noexcept return data_[i];

      T& front() return data_.front().front();
      const T& front() const return data_.front().front();

      T& back() return data_.back().back();
      const T& back() const return data_.back().back();

      T* data() noexcept return data_.data()->data();
      const T* data() const noexcept return data_.data()->data();

      T* begin() noexcept return data();
      const T* begin() const noexcept return data();

      T* end() noexcept return data() + size();
      const T* end() const noexcept return data() + size();

      auto rbegin() noexcept return std::make_reverse_iterator(end());
      auto rbegin() const noexcept return std::make_reverse_iterator(end());

      auto rend() noexcept return std::make_reverse_iterator(begin());
      auto rend() const noexcept return std::make_reverse_iterator(begin());

      void fill(const T& v)
      for (auto& row : data_)
      row.fill(v);



      friend void swap(array2d& a, array2d& b) a.data_.swap(b.data_);

      std::array<row_t, N0> data_;
      ;









      share|improve this question









      $endgroup$




      I implemented a 2D counterpart of std::array named array2d in C++17. It is an aggregate like std::array, and provides similar interface. The goal is that if you know how to use std::array, then you will find yourself at home using array2d. Any comments are welcome :) For better viewing experience with highlighting, you can refer to this GitHub page.



      #include <cstddef>
      #include <array>
      #include <iterator>

      template <typename T, std::size_t N0, std::size_t N1>
      struct array2d
      using row_t = std::array<T, N1>;
      inline static constexpr std::array sizes N0, N1 ;

      static constexpr std::size_t size() noexcept return N0 * N1;
      static constexpr bool empty() noexcept return !size();

      T& at(std::size_t i, std::size_t j) return data_.at(i).at(j);
      const T& at(std::size_t i, std::size_t j) const return data_.at(i).at(j);

      row_t& operator(std::size_t i) noexcept return data_[i];
      const row_t& operator(std::size_t i) const noexcept return data_[i];

      T& front() return data_.front().front();
      const T& front() const return data_.front().front();

      T& back() return data_.back().back();
      const T& back() const return data_.back().back();

      T* data() noexcept return data_.data()->data();
      const T* data() const noexcept return data_.data()->data();

      T* begin() noexcept return data();
      const T* begin() const noexcept return data();

      T* end() noexcept return data() + size();
      const T* end() const noexcept return data() + size();

      auto rbegin() noexcept return std::make_reverse_iterator(end());
      auto rbegin() const noexcept return std::make_reverse_iterator(end());

      auto rend() noexcept return std::make_reverse_iterator(begin());
      auto rend() const noexcept return std::make_reverse_iterator(begin());

      void fill(const T& v)
      for (auto& row : data_)
      row.fill(v);



      friend void swap(array2d& a, array2d& b) a.data_.swap(b.data_);

      std::array<row_t, N0> data_;
      ;






      c++ library template-meta-programming c++17






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Mar 14 at 6:52









      LingxiLingxi

      466215




      466215




















          2 Answers
          2






          active

          oldest

          votes


















          16












          $begingroup$

          Let me collect a couple of thoughts here.




          • Aggregate initialization currently works like this:



            array2d<int, 2, 2> a1, 2, 3, 4;


            but wouldn't it be favorable to allow for



            array2d<int, 2, 2> a1, 2, 3, 4;


          • std::array::at performs bound checking and throws upon an out of bounds index. When your intention is to stick with the std::array interface, you should do the same.


          • If you want the container to be standard-compliant, there are some type aliases missing and maybe more. In particular, there are no cbegin(), cend(), crbegin(), crend() member functions. Is this intended?


          • You implicitly use row-major order. Are you sure everyone expects this? Users familiar with Eigen and their fixed size matrices might at least want to customize row-/column-major ordering, e.g. Eigen::Matrix<int, 2, 2, Eigen::ColMajor> m;



          • A range based for loop will considerably differ from a manual loop over rows and columns. Example:



            // Loop over elements, transposed access. Requires nested loop.
            for (std::size_t i = 0; i < 2; ++i)
            for (std::size_t j = 0; j < 2; ++j)
            std::cout << a[j][i] << "n";

            // Loop over elements, tranposed access impossible. Only one loop.
            for (const auto& i : d)
            std::cout << i << "n";


            This is slightly unintuitive. Shouldn't the range based for loop require a nested loop as well?



          • The static data member sizes is not used anywhere.


          Getting a two-dimensional array to work is not that much of an effort. Getting the semantics right is hard. Sticking to the std::array interface is a good goal when ease of use is intended for those familiar with the std::array template. But the additional dimension pulls in requirements that can't be tackled with the concepts of std::array. I would recommend having a look at established linear algebra libraries and their fixed size matrices. Also, the mdspan proposal for a multi-dimensional view on array types might be a good read.






          share|improve this answer











          $endgroup$












          • $begingroup$
            Thanks for reviewing my code. 1) You can a1, 2, 3, 4 and make a new line as you see fit. 2) array2d::at does bounds checking too, for array::at is called under the hood ^_^ 3) You are right they are missing. I think they are tedious and not very useful in practice. But again, the array2d is not very useful either XD 4) It is by design that the layout aligns with built-in two-dimensional array. 5) I intend the loop to be similar to what it does with array. User needs to use other interface if different traversal is desired. 6) sizes is part of the interface.
            $endgroup$
            – Lingxi
            Mar 14 at 11:16










          • $begingroup$
            array2d is not meant to model matrix in linear algebra. Just like array and vector are not meant to model vector in linear algebra. By design, it's only meant to serve the role of a basic fixed-size 2D container. Hopefully more convenient to use than built-in 2D array and nested array.
            $endgroup$
            – Lingxi
            Mar 14 at 11:16










          • $begingroup$
            You're right with the bounds checking of course :) Also, the point that array2d is not meant to be used as a linear algebra vocabulary type is obviously valid. I do see two issues with that, though: if you introduce array2d to a code base, developers might use it for linear algebra despite the fact that you didn't design it to fit these requirements. And, I personally use two-dimensional arrays for linear algebra and nothing else. If I need more that one dimension, I often find other data structures nearer to my intention.
            $endgroup$
            – lubgr
            Mar 14 at 11:28


















          2












          $begingroup$

          Looks good! Great job.




          1. To initialize the array completely I have to write:



            array2d<int, 2, 2> Array1, 2, 2, 3;


            The two extra sets of braces are horrible! If you instead have a T data member one layer of braces falls of and you need only one set of braces just like std::array.



            array2d<int, 2, 2> Array1, 2, 2, 3; // manageable


          2. Whatever happened to constexpr all the things? :)


          3. Nested std::arrays are not guaranteed to be continuous (see this post), although in practice they probably are. Resolving point 1) also fixes this issue.


          4. IMO a at member that takes only one index and returns a row would make sense for consistency with your operator.


          5. Consider adding the various member types that a Container is supposed to have (and also the other requirements, cr[begin, end], max_size, member swap, ...).


          6. I mean sure, size and empty can be static, but really, conceptually this doesn't make much sense. std::array's empty and size are not static too.


          7. How about providing various customization points of std::get, std::tuple_size, ... so that your array works with structured bindings.


          8. You didn't add any relational operators. Is this intentional?






          share|improve this answer









          $endgroup$












          • $begingroup$
            Thanks for reviewing my code, Rakete. 1) Since C++14, you can simply Array1, 2, 4, 5 without any nested braces. 2) You are right they are missing. It's tedious and a burden, and I don't think they are very useful in practice. So I don't bother :/ 3) Good catch. Today I learned. 4) On a second thought, agreed. We can have at overloads that take 1 and 2 indices ^_^ 5) Yes. For serious production code, they should be present. The boring part of writing C++ library code XD
            $endgroup$
            – Lingxi
            Mar 15 at 1:56










          • $begingroup$
            6) User can still invoke them the way as if they are non-static like a.size(). Making them static provides more possibilities, and removes the need to pass this. 7) You are right. 8) Not until operator<=> is practically supported by the compilers :P
            $endgroup$
            – Lingxi
            Mar 15 at 2:01










          • $begingroup$
            @Lingxi 1) How did I not know that, thanks :) 2) you really should, it's only one keyword ;) 6) yeah, but still thinks it's weird. 8) that would require that T also has an operator<=>. If it has the traditional operators, then you won't be able to compare the array since operator<=> can't dispatch to the individual relational operators of T.
            $endgroup$
            – Rakete1111
            Mar 15 at 9:04











          Your Answer






          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader:
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          ,
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );













          draft saved

          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f215399%2f2d-counterpart-of-stdarray-in-c17%23new-answer', 'question_page');

          );

          Post as a guest















          Required, but never shown

























          2 Answers
          2






          active

          oldest

          votes








          2 Answers
          2






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          16












          $begingroup$

          Let me collect a couple of thoughts here.




          • Aggregate initialization currently works like this:



            array2d<int, 2, 2> a1, 2, 3, 4;


            but wouldn't it be favorable to allow for



            array2d<int, 2, 2> a1, 2, 3, 4;


          • std::array::at performs bound checking and throws upon an out of bounds index. When your intention is to stick with the std::array interface, you should do the same.


          • If you want the container to be standard-compliant, there are some type aliases missing and maybe more. In particular, there are no cbegin(), cend(), crbegin(), crend() member functions. Is this intended?


          • You implicitly use row-major order. Are you sure everyone expects this? Users familiar with Eigen and their fixed size matrices might at least want to customize row-/column-major ordering, e.g. Eigen::Matrix<int, 2, 2, Eigen::ColMajor> m;



          • A range based for loop will considerably differ from a manual loop over rows and columns. Example:



            // Loop over elements, transposed access. Requires nested loop.
            for (std::size_t i = 0; i < 2; ++i)
            for (std::size_t j = 0; j < 2; ++j)
            std::cout << a[j][i] << "n";

            // Loop over elements, tranposed access impossible. Only one loop.
            for (const auto& i : d)
            std::cout << i << "n";


            This is slightly unintuitive. Shouldn't the range based for loop require a nested loop as well?



          • The static data member sizes is not used anywhere.


          Getting a two-dimensional array to work is not that much of an effort. Getting the semantics right is hard. Sticking to the std::array interface is a good goal when ease of use is intended for those familiar with the std::array template. But the additional dimension pulls in requirements that can't be tackled with the concepts of std::array. I would recommend having a look at established linear algebra libraries and their fixed size matrices. Also, the mdspan proposal for a multi-dimensional view on array types might be a good read.






          share|improve this answer











          $endgroup$












          • $begingroup$
            Thanks for reviewing my code. 1) You can a1, 2, 3, 4 and make a new line as you see fit. 2) array2d::at does bounds checking too, for array::at is called under the hood ^_^ 3) You are right they are missing. I think they are tedious and not very useful in practice. But again, the array2d is not very useful either XD 4) It is by design that the layout aligns with built-in two-dimensional array. 5) I intend the loop to be similar to what it does with array. User needs to use other interface if different traversal is desired. 6) sizes is part of the interface.
            $endgroup$
            – Lingxi
            Mar 14 at 11:16










          • $begingroup$
            array2d is not meant to model matrix in linear algebra. Just like array and vector are not meant to model vector in linear algebra. By design, it's only meant to serve the role of a basic fixed-size 2D container. Hopefully more convenient to use than built-in 2D array and nested array.
            $endgroup$
            – Lingxi
            Mar 14 at 11:16










          • $begingroup$
            You're right with the bounds checking of course :) Also, the point that array2d is not meant to be used as a linear algebra vocabulary type is obviously valid. I do see two issues with that, though: if you introduce array2d to a code base, developers might use it for linear algebra despite the fact that you didn't design it to fit these requirements. And, I personally use two-dimensional arrays for linear algebra and nothing else. If I need more that one dimension, I often find other data structures nearer to my intention.
            $endgroup$
            – lubgr
            Mar 14 at 11:28















          16












          $begingroup$

          Let me collect a couple of thoughts here.




          • Aggregate initialization currently works like this:



            array2d<int, 2, 2> a1, 2, 3, 4;


            but wouldn't it be favorable to allow for



            array2d<int, 2, 2> a1, 2, 3, 4;


          • std::array::at performs bound checking and throws upon an out of bounds index. When your intention is to stick with the std::array interface, you should do the same.


          • If you want the container to be standard-compliant, there are some type aliases missing and maybe more. In particular, there are no cbegin(), cend(), crbegin(), crend() member functions. Is this intended?


          • You implicitly use row-major order. Are you sure everyone expects this? Users familiar with Eigen and their fixed size matrices might at least want to customize row-/column-major ordering, e.g. Eigen::Matrix<int, 2, 2, Eigen::ColMajor> m;



          • A range based for loop will considerably differ from a manual loop over rows and columns. Example:



            // Loop over elements, transposed access. Requires nested loop.
            for (std::size_t i = 0; i < 2; ++i)
            for (std::size_t j = 0; j < 2; ++j)
            std::cout << a[j][i] << "n";

            // Loop over elements, tranposed access impossible. Only one loop.
            for (const auto& i : d)
            std::cout << i << "n";


            This is slightly unintuitive. Shouldn't the range based for loop require a nested loop as well?



          • The static data member sizes is not used anywhere.


          Getting a two-dimensional array to work is not that much of an effort. Getting the semantics right is hard. Sticking to the std::array interface is a good goal when ease of use is intended for those familiar with the std::array template. But the additional dimension pulls in requirements that can't be tackled with the concepts of std::array. I would recommend having a look at established linear algebra libraries and their fixed size matrices. Also, the mdspan proposal for a multi-dimensional view on array types might be a good read.






          share|improve this answer











          $endgroup$












          • $begingroup$
            Thanks for reviewing my code. 1) You can a1, 2, 3, 4 and make a new line as you see fit. 2) array2d::at does bounds checking too, for array::at is called under the hood ^_^ 3) You are right they are missing. I think they are tedious and not very useful in practice. But again, the array2d is not very useful either XD 4) It is by design that the layout aligns with built-in two-dimensional array. 5) I intend the loop to be similar to what it does with array. User needs to use other interface if different traversal is desired. 6) sizes is part of the interface.
            $endgroup$
            – Lingxi
            Mar 14 at 11:16










          • $begingroup$
            array2d is not meant to model matrix in linear algebra. Just like array and vector are not meant to model vector in linear algebra. By design, it's only meant to serve the role of a basic fixed-size 2D container. Hopefully more convenient to use than built-in 2D array and nested array.
            $endgroup$
            – Lingxi
            Mar 14 at 11:16










          • $begingroup$
            You're right with the bounds checking of course :) Also, the point that array2d is not meant to be used as a linear algebra vocabulary type is obviously valid. I do see two issues with that, though: if you introduce array2d to a code base, developers might use it for linear algebra despite the fact that you didn't design it to fit these requirements. And, I personally use two-dimensional arrays for linear algebra and nothing else. If I need more that one dimension, I often find other data structures nearer to my intention.
            $endgroup$
            – lubgr
            Mar 14 at 11:28













          16












          16








          16





          $begingroup$

          Let me collect a couple of thoughts here.




          • Aggregate initialization currently works like this:



            array2d<int, 2, 2> a1, 2, 3, 4;


            but wouldn't it be favorable to allow for



            array2d<int, 2, 2> a1, 2, 3, 4;


          • std::array::at performs bound checking and throws upon an out of bounds index. When your intention is to stick with the std::array interface, you should do the same.


          • If you want the container to be standard-compliant, there are some type aliases missing and maybe more. In particular, there are no cbegin(), cend(), crbegin(), crend() member functions. Is this intended?


          • You implicitly use row-major order. Are you sure everyone expects this? Users familiar with Eigen and their fixed size matrices might at least want to customize row-/column-major ordering, e.g. Eigen::Matrix<int, 2, 2, Eigen::ColMajor> m;



          • A range based for loop will considerably differ from a manual loop over rows and columns. Example:



            // Loop over elements, transposed access. Requires nested loop.
            for (std::size_t i = 0; i < 2; ++i)
            for (std::size_t j = 0; j < 2; ++j)
            std::cout << a[j][i] << "n";

            // Loop over elements, tranposed access impossible. Only one loop.
            for (const auto& i : d)
            std::cout << i << "n";


            This is slightly unintuitive. Shouldn't the range based for loop require a nested loop as well?



          • The static data member sizes is not used anywhere.


          Getting a two-dimensional array to work is not that much of an effort. Getting the semantics right is hard. Sticking to the std::array interface is a good goal when ease of use is intended for those familiar with the std::array template. But the additional dimension pulls in requirements that can't be tackled with the concepts of std::array. I would recommend having a look at established linear algebra libraries and their fixed size matrices. Also, the mdspan proposal for a multi-dimensional view on array types might be a good read.






          share|improve this answer











          $endgroup$



          Let me collect a couple of thoughts here.




          • Aggregate initialization currently works like this:



            array2d<int, 2, 2> a1, 2, 3, 4;


            but wouldn't it be favorable to allow for



            array2d<int, 2, 2> a1, 2, 3, 4;


          • std::array::at performs bound checking and throws upon an out of bounds index. When your intention is to stick with the std::array interface, you should do the same.


          • If you want the container to be standard-compliant, there are some type aliases missing and maybe more. In particular, there are no cbegin(), cend(), crbegin(), crend() member functions. Is this intended?


          • You implicitly use row-major order. Are you sure everyone expects this? Users familiar with Eigen and their fixed size matrices might at least want to customize row-/column-major ordering, e.g. Eigen::Matrix<int, 2, 2, Eigen::ColMajor> m;



          • A range based for loop will considerably differ from a manual loop over rows and columns. Example:



            // Loop over elements, transposed access. Requires nested loop.
            for (std::size_t i = 0; i < 2; ++i)
            for (std::size_t j = 0; j < 2; ++j)
            std::cout << a[j][i] << "n";

            // Loop over elements, tranposed access impossible. Only one loop.
            for (const auto& i : d)
            std::cout << i << "n";


            This is slightly unintuitive. Shouldn't the range based for loop require a nested loop as well?



          • The static data member sizes is not used anywhere.


          Getting a two-dimensional array to work is not that much of an effort. Getting the semantics right is hard. Sticking to the std::array interface is a good goal when ease of use is intended for those familiar with the std::array template. But the additional dimension pulls in requirements that can't be tackled with the concepts of std::array. I would recommend having a look at established linear algebra libraries and their fixed size matrices. Also, the mdspan proposal for a multi-dimensional view on array types might be a good read.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Mar 14 at 10:00

























          answered Mar 14 at 8:36









          lubgrlubgr

          6789




          6789











          • $begingroup$
            Thanks for reviewing my code. 1) You can a1, 2, 3, 4 and make a new line as you see fit. 2) array2d::at does bounds checking too, for array::at is called under the hood ^_^ 3) You are right they are missing. I think they are tedious and not very useful in practice. But again, the array2d is not very useful either XD 4) It is by design that the layout aligns with built-in two-dimensional array. 5) I intend the loop to be similar to what it does with array. User needs to use other interface if different traversal is desired. 6) sizes is part of the interface.
            $endgroup$
            – Lingxi
            Mar 14 at 11:16










          • $begingroup$
            array2d is not meant to model matrix in linear algebra. Just like array and vector are not meant to model vector in linear algebra. By design, it's only meant to serve the role of a basic fixed-size 2D container. Hopefully more convenient to use than built-in 2D array and nested array.
            $endgroup$
            – Lingxi
            Mar 14 at 11:16










          • $begingroup$
            You're right with the bounds checking of course :) Also, the point that array2d is not meant to be used as a linear algebra vocabulary type is obviously valid. I do see two issues with that, though: if you introduce array2d to a code base, developers might use it for linear algebra despite the fact that you didn't design it to fit these requirements. And, I personally use two-dimensional arrays for linear algebra and nothing else. If I need more that one dimension, I often find other data structures nearer to my intention.
            $endgroup$
            – lubgr
            Mar 14 at 11:28
















          • $begingroup$
            Thanks for reviewing my code. 1) You can a1, 2, 3, 4 and make a new line as you see fit. 2) array2d::at does bounds checking too, for array::at is called under the hood ^_^ 3) You are right they are missing. I think they are tedious and not very useful in practice. But again, the array2d is not very useful either XD 4) It is by design that the layout aligns with built-in two-dimensional array. 5) I intend the loop to be similar to what it does with array. User needs to use other interface if different traversal is desired. 6) sizes is part of the interface.
            $endgroup$
            – Lingxi
            Mar 14 at 11:16










          • $begingroup$
            array2d is not meant to model matrix in linear algebra. Just like array and vector are not meant to model vector in linear algebra. By design, it's only meant to serve the role of a basic fixed-size 2D container. Hopefully more convenient to use than built-in 2D array and nested array.
            $endgroup$
            – Lingxi
            Mar 14 at 11:16










          • $begingroup$
            You're right with the bounds checking of course :) Also, the point that array2d is not meant to be used as a linear algebra vocabulary type is obviously valid. I do see two issues with that, though: if you introduce array2d to a code base, developers might use it for linear algebra despite the fact that you didn't design it to fit these requirements. And, I personally use two-dimensional arrays for linear algebra and nothing else. If I need more that one dimension, I often find other data structures nearer to my intention.
            $endgroup$
            – lubgr
            Mar 14 at 11:28















          $begingroup$
          Thanks for reviewing my code. 1) You can a1, 2, 3, 4 and make a new line as you see fit. 2) array2d::at does bounds checking too, for array::at is called under the hood ^_^ 3) You are right they are missing. I think they are tedious and not very useful in practice. But again, the array2d is not very useful either XD 4) It is by design that the layout aligns with built-in two-dimensional array. 5) I intend the loop to be similar to what it does with array. User needs to use other interface if different traversal is desired. 6) sizes is part of the interface.
          $endgroup$
          – Lingxi
          Mar 14 at 11:16




          $begingroup$
          Thanks for reviewing my code. 1) You can a1, 2, 3, 4 and make a new line as you see fit. 2) array2d::at does bounds checking too, for array::at is called under the hood ^_^ 3) You are right they are missing. I think they are tedious and not very useful in practice. But again, the array2d is not very useful either XD 4) It is by design that the layout aligns with built-in two-dimensional array. 5) I intend the loop to be similar to what it does with array. User needs to use other interface if different traversal is desired. 6) sizes is part of the interface.
          $endgroup$
          – Lingxi
          Mar 14 at 11:16












          $begingroup$
          array2d is not meant to model matrix in linear algebra. Just like array and vector are not meant to model vector in linear algebra. By design, it's only meant to serve the role of a basic fixed-size 2D container. Hopefully more convenient to use than built-in 2D array and nested array.
          $endgroup$
          – Lingxi
          Mar 14 at 11:16




          $begingroup$
          array2d is not meant to model matrix in linear algebra. Just like array and vector are not meant to model vector in linear algebra. By design, it's only meant to serve the role of a basic fixed-size 2D container. Hopefully more convenient to use than built-in 2D array and nested array.
          $endgroup$
          – Lingxi
          Mar 14 at 11:16












          $begingroup$
          You're right with the bounds checking of course :) Also, the point that array2d is not meant to be used as a linear algebra vocabulary type is obviously valid. I do see two issues with that, though: if you introduce array2d to a code base, developers might use it for linear algebra despite the fact that you didn't design it to fit these requirements. And, I personally use two-dimensional arrays for linear algebra and nothing else. If I need more that one dimension, I often find other data structures nearer to my intention.
          $endgroup$
          – lubgr
          Mar 14 at 11:28




          $begingroup$
          You're right with the bounds checking of course :) Also, the point that array2d is not meant to be used as a linear algebra vocabulary type is obviously valid. I do see two issues with that, though: if you introduce array2d to a code base, developers might use it for linear algebra despite the fact that you didn't design it to fit these requirements. And, I personally use two-dimensional arrays for linear algebra and nothing else. If I need more that one dimension, I often find other data structures nearer to my intention.
          $endgroup$
          – lubgr
          Mar 14 at 11:28













          2












          $begingroup$

          Looks good! Great job.




          1. To initialize the array completely I have to write:



            array2d<int, 2, 2> Array1, 2, 2, 3;


            The two extra sets of braces are horrible! If you instead have a T data member one layer of braces falls of and you need only one set of braces just like std::array.



            array2d<int, 2, 2> Array1, 2, 2, 3; // manageable


          2. Whatever happened to constexpr all the things? :)


          3. Nested std::arrays are not guaranteed to be continuous (see this post), although in practice they probably are. Resolving point 1) also fixes this issue.


          4. IMO a at member that takes only one index and returns a row would make sense for consistency with your operator.


          5. Consider adding the various member types that a Container is supposed to have (and also the other requirements, cr[begin, end], max_size, member swap, ...).


          6. I mean sure, size and empty can be static, but really, conceptually this doesn't make much sense. std::array's empty and size are not static too.


          7. How about providing various customization points of std::get, std::tuple_size, ... so that your array works with structured bindings.


          8. You didn't add any relational operators. Is this intentional?






          share|improve this answer









          $endgroup$












          • $begingroup$
            Thanks for reviewing my code, Rakete. 1) Since C++14, you can simply Array1, 2, 4, 5 without any nested braces. 2) You are right they are missing. It's tedious and a burden, and I don't think they are very useful in practice. So I don't bother :/ 3) Good catch. Today I learned. 4) On a second thought, agreed. We can have at overloads that take 1 and 2 indices ^_^ 5) Yes. For serious production code, they should be present. The boring part of writing C++ library code XD
            $endgroup$
            – Lingxi
            Mar 15 at 1:56










          • $begingroup$
            6) User can still invoke them the way as if they are non-static like a.size(). Making them static provides more possibilities, and removes the need to pass this. 7) You are right. 8) Not until operator<=> is practically supported by the compilers :P
            $endgroup$
            – Lingxi
            Mar 15 at 2:01










          • $begingroup$
            @Lingxi 1) How did I not know that, thanks :) 2) you really should, it's only one keyword ;) 6) yeah, but still thinks it's weird. 8) that would require that T also has an operator<=>. If it has the traditional operators, then you won't be able to compare the array since operator<=> can't dispatch to the individual relational operators of T.
            $endgroup$
            – Rakete1111
            Mar 15 at 9:04















          2












          $begingroup$

          Looks good! Great job.




          1. To initialize the array completely I have to write:



            array2d<int, 2, 2> Array1, 2, 2, 3;


            The two extra sets of braces are horrible! If you instead have a T data member one layer of braces falls of and you need only one set of braces just like std::array.



            array2d<int, 2, 2> Array1, 2, 2, 3; // manageable


          2. Whatever happened to constexpr all the things? :)


          3. Nested std::arrays are not guaranteed to be continuous (see this post), although in practice they probably are. Resolving point 1) also fixes this issue.


          4. IMO a at member that takes only one index and returns a row would make sense for consistency with your operator.


          5. Consider adding the various member types that a Container is supposed to have (and also the other requirements, cr[begin, end], max_size, member swap, ...).


          6. I mean sure, size and empty can be static, but really, conceptually this doesn't make much sense. std::array's empty and size are not static too.


          7. How about providing various customization points of std::get, std::tuple_size, ... so that your array works with structured bindings.


          8. You didn't add any relational operators. Is this intentional?






          share|improve this answer









          $endgroup$












          • $begingroup$
            Thanks for reviewing my code, Rakete. 1) Since C++14, you can simply Array1, 2, 4, 5 without any nested braces. 2) You are right they are missing. It's tedious and a burden, and I don't think they are very useful in practice. So I don't bother :/ 3) Good catch. Today I learned. 4) On a second thought, agreed. We can have at overloads that take 1 and 2 indices ^_^ 5) Yes. For serious production code, they should be present. The boring part of writing C++ library code XD
            $endgroup$
            – Lingxi
            Mar 15 at 1:56










          • $begingroup$
            6) User can still invoke them the way as if they are non-static like a.size(). Making them static provides more possibilities, and removes the need to pass this. 7) You are right. 8) Not until operator<=> is practically supported by the compilers :P
            $endgroup$
            – Lingxi
            Mar 15 at 2:01










          • $begingroup$
            @Lingxi 1) How did I not know that, thanks :) 2) you really should, it's only one keyword ;) 6) yeah, but still thinks it's weird. 8) that would require that T also has an operator<=>. If it has the traditional operators, then you won't be able to compare the array since operator<=> can't dispatch to the individual relational operators of T.
            $endgroup$
            – Rakete1111
            Mar 15 at 9:04













          2












          2








          2





          $begingroup$

          Looks good! Great job.




          1. To initialize the array completely I have to write:



            array2d<int, 2, 2> Array1, 2, 2, 3;


            The two extra sets of braces are horrible! If you instead have a T data member one layer of braces falls of and you need only one set of braces just like std::array.



            array2d<int, 2, 2> Array1, 2, 2, 3; // manageable


          2. Whatever happened to constexpr all the things? :)


          3. Nested std::arrays are not guaranteed to be continuous (see this post), although in practice they probably are. Resolving point 1) also fixes this issue.


          4. IMO a at member that takes only one index and returns a row would make sense for consistency with your operator.


          5. Consider adding the various member types that a Container is supposed to have (and also the other requirements, cr[begin, end], max_size, member swap, ...).


          6. I mean sure, size and empty can be static, but really, conceptually this doesn't make much sense. std::array's empty and size are not static too.


          7. How about providing various customization points of std::get, std::tuple_size, ... so that your array works with structured bindings.


          8. You didn't add any relational operators. Is this intentional?






          share|improve this answer









          $endgroup$



          Looks good! Great job.




          1. To initialize the array completely I have to write:



            array2d<int, 2, 2> Array1, 2, 2, 3;


            The two extra sets of braces are horrible! If you instead have a T data member one layer of braces falls of and you need only one set of braces just like std::array.



            array2d<int, 2, 2> Array1, 2, 2, 3; // manageable


          2. Whatever happened to constexpr all the things? :)


          3. Nested std::arrays are not guaranteed to be continuous (see this post), although in practice they probably are. Resolving point 1) also fixes this issue.


          4. IMO a at member that takes only one index and returns a row would make sense for consistency with your operator.


          5. Consider adding the various member types that a Container is supposed to have (and also the other requirements, cr[begin, end], max_size, member swap, ...).


          6. I mean sure, size and empty can be static, but really, conceptually this doesn't make much sense. std::array's empty and size are not static too.


          7. How about providing various customization points of std::get, std::tuple_size, ... so that your array works with structured bindings.


          8. You didn't add any relational operators. Is this intentional?







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Mar 14 at 22:11









          Rakete1111Rakete1111

          2,2641822




          2,2641822











          • $begingroup$
            Thanks for reviewing my code, Rakete. 1) Since C++14, you can simply Array1, 2, 4, 5 without any nested braces. 2) You are right they are missing. It's tedious and a burden, and I don't think they are very useful in practice. So I don't bother :/ 3) Good catch. Today I learned. 4) On a second thought, agreed. We can have at overloads that take 1 and 2 indices ^_^ 5) Yes. For serious production code, they should be present. The boring part of writing C++ library code XD
            $endgroup$
            – Lingxi
            Mar 15 at 1:56










          • $begingroup$
            6) User can still invoke them the way as if they are non-static like a.size(). Making them static provides more possibilities, and removes the need to pass this. 7) You are right. 8) Not until operator<=> is practically supported by the compilers :P
            $endgroup$
            – Lingxi
            Mar 15 at 2:01










          • $begingroup$
            @Lingxi 1) How did I not know that, thanks :) 2) you really should, it's only one keyword ;) 6) yeah, but still thinks it's weird. 8) that would require that T also has an operator<=>. If it has the traditional operators, then you won't be able to compare the array since operator<=> can't dispatch to the individual relational operators of T.
            $endgroup$
            – Rakete1111
            Mar 15 at 9:04
















          • $begingroup$
            Thanks for reviewing my code, Rakete. 1) Since C++14, you can simply Array1, 2, 4, 5 without any nested braces. 2) You are right they are missing. It's tedious and a burden, and I don't think they are very useful in practice. So I don't bother :/ 3) Good catch. Today I learned. 4) On a second thought, agreed. We can have at overloads that take 1 and 2 indices ^_^ 5) Yes. For serious production code, they should be present. The boring part of writing C++ library code XD
            $endgroup$
            – Lingxi
            Mar 15 at 1:56










          • $begingroup$
            6) User can still invoke them the way as if they are non-static like a.size(). Making them static provides more possibilities, and removes the need to pass this. 7) You are right. 8) Not until operator<=> is practically supported by the compilers :P
            $endgroup$
            – Lingxi
            Mar 15 at 2:01










          • $begingroup$
            @Lingxi 1) How did I not know that, thanks :) 2) you really should, it's only one keyword ;) 6) yeah, but still thinks it's weird. 8) that would require that T also has an operator<=>. If it has the traditional operators, then you won't be able to compare the array since operator<=> can't dispatch to the individual relational operators of T.
            $endgroup$
            – Rakete1111
            Mar 15 at 9:04















          $begingroup$
          Thanks for reviewing my code, Rakete. 1) Since C++14, you can simply Array1, 2, 4, 5 without any nested braces. 2) You are right they are missing. It's tedious and a burden, and I don't think they are very useful in practice. So I don't bother :/ 3) Good catch. Today I learned. 4) On a second thought, agreed. We can have at overloads that take 1 and 2 indices ^_^ 5) Yes. For serious production code, they should be present. The boring part of writing C++ library code XD
          $endgroup$
          – Lingxi
          Mar 15 at 1:56




          $begingroup$
          Thanks for reviewing my code, Rakete. 1) Since C++14, you can simply Array1, 2, 4, 5 without any nested braces. 2) You are right they are missing. It's tedious and a burden, and I don't think they are very useful in practice. So I don't bother :/ 3) Good catch. Today I learned. 4) On a second thought, agreed. We can have at overloads that take 1 and 2 indices ^_^ 5) Yes. For serious production code, they should be present. The boring part of writing C++ library code XD
          $endgroup$
          – Lingxi
          Mar 15 at 1:56












          $begingroup$
          6) User can still invoke them the way as if they are non-static like a.size(). Making them static provides more possibilities, and removes the need to pass this. 7) You are right. 8) Not until operator<=> is practically supported by the compilers :P
          $endgroup$
          – Lingxi
          Mar 15 at 2:01




          $begingroup$
          6) User can still invoke them the way as if they are non-static like a.size(). Making them static provides more possibilities, and removes the need to pass this. 7) You are right. 8) Not until operator<=> is practically supported by the compilers :P
          $endgroup$
          – Lingxi
          Mar 15 at 2:01












          $begingroup$
          @Lingxi 1) How did I not know that, thanks :) 2) you really should, it's only one keyword ;) 6) yeah, but still thinks it's weird. 8) that would require that T also has an operator<=>. If it has the traditional operators, then you won't be able to compare the array since operator<=> can't dispatch to the individual relational operators of T.
          $endgroup$
          – Rakete1111
          Mar 15 at 9:04




          $begingroup$
          @Lingxi 1) How did I not know that, thanks :) 2) you really should, it's only one keyword ;) 6) yeah, but still thinks it's weird. 8) that would require that T also has an operator<=>. If it has the traditional operators, then you won't be able to compare the array since operator<=> can't dispatch to the individual relational operators of T.
          $endgroup$
          – Rakete1111
          Mar 15 at 9:04

















          draft saved

          draft discarded
















































          Thanks for contributing an answer to Code Review Stack Exchange!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid


          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.

          Use MathJax to format equations. MathJax reference.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f215399%2f2d-counterpart-of-stdarray-in-c17%23new-answer', 'question_page');

          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown






          Popular posts from this blog

          How to check contact read email or not when send email to Individual?

          Displaying single band from multi-band raster using QGIS

          How many registers does an x86_64 CPU actually have?