2
\$\begingroup\$

Edited with a bit more unit tests.

Preface

Following my second version:
2d Grid - Iterating by Rows / Cells - Take #2

I believe that I am now satisfied with this new version below.
There is still one area which I question whether it is a code smell or not.
A code review would also be much appreciated.

The solved problem

The need is to represent pixel data in contiguous memory, and seamlessly allow for iteration over rows / columns using range-for with such as

for(auto&& row : bitmap)
    for(auto &&pixel : row)
        //do something with pixel

Without this iterating would require one range-for over the whole data and manually keeping track of which row we are at.

All code is C++20 and should be efficient and nice and work on a const objects as well.
The grid is working and tested, but please help me find code smell and improvements.

How was the problem solved

To solve this, a generic Grid<T> is used.
The Grid itself is meant to be an STL container
https://en.cppreference.com/w/cpp/named_req/Container

This Grid object is templated and can be used on any custom data.
Unit tests are present for a complete code coverage.

The remaining code smell, maybe

RowIterator inherit ConstRowIterator, and the former is using const_cast in operator* and operator->. This is also how the Microsoft STL vector implementation does it. Is the const_cast needed because of a bad design, or is it justified in this situation?

Same for Rows inheriting from ConstRows, but this time ConstRows also has a few protected variables reused in Rows. Again is that justified or is it needed because of a bad design?

Code Source - The Grid - grid.hpp

#ifndef SHOUJIN_SOURCE_GRID
#define SHOUJIN_SOURCE_GRID

#include <algorithm>
#include <initializer_list>
#include <iterator>
#include <utility>
#include <vector>

namespace shoujin {

/// <summary>
/// Grid is a Container
/// https://en.cppreference.com/w/cpp/named_req/Container
/// </summary>
template<typename T>
class Grid {
public:
    class Row {
    public:
        using value_type = T;
        using pointer = value_type*;
        using const_pointer = value_type const*;
        using reference = value_type&;
        using const_reference = value_type const&;
        using iterator = pointer;
        using const_iterator = const_pointer;

        constexpr Row() = default;

        constexpr Row(pointer begin, pointer end) :
            _begin{begin},
            _end{end} {}

        [[nodiscard]] constexpr iterator begin() noexcept { return _begin; }
        [[nodiscard]] constexpr iterator end() noexcept { return _end; };
        [[nodiscard]] constexpr const_iterator begin() const noexcept { return _begin; }
        [[nodiscard]] constexpr const_iterator end() const noexcept { return _end; };
        [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return _begin; }
        [[nodiscard]] constexpr const_iterator cend() const noexcept { return _end; };

        [[nodiscard]] constexpr size_t size() const noexcept { return _end - _begin; }
        [[nodiscard]] constexpr reference operator[](size_t idx) noexcept { return *(_begin + idx); };
        [[nodiscard]] constexpr const_reference operator[](size_t idx) const noexcept { return *(_begin + idx); };

        [[nodiscard]] constexpr friend bool operator==(Row const& lhs, Row const& rhs)
        {
            return lhs._begin == rhs._begin && lhs._end == rhs._end;
        }

    private:
        pointer _begin{};
        pointer _end{};
    };

    /// <summary>
    /// ConstRowIterator is a LegacyBidirectionalIterator
    /// https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator
    /// </summary>
    class ConstRowIterator {
    public:
        using value_type = Row;
        using pointer = value_type const*;
        using reference = value_type const&;
        using difference_type = ptrdiff_t;
        using iterator_category = std::random_access_iterator_tag;

        constexpr ConstRowIterator() = default;

        constexpr ConstRowIterator(T* begin, size_t row_length) :
            _begin{begin},
            _row{begin, begin + row_length},
            _row_length{row_length}
        {}

        constexpr void swap(ConstRowIterator& right) noexcept
        {
            if(this != &right) {
                using std::swap;
                swap(_row, right._row);
                swap(_row_length, right._row_length);
            }
        }

        constexpr friend void swap(ConstRowIterator& left, ConstRowIterator& right) noexcept
        {
            left.swap(right);
        }

        [[nodiscard]] constexpr value_type operator[](size_t idx) const noexcept { return {_begin, _begin + _row_length}; }
        [[nodiscard]] constexpr reference operator*() const noexcept { return _row; }
        [[nodiscard]] constexpr pointer operator->() const noexcept { return &_row; }

        constexpr ConstRowIterator& operator++() noexcept
        {
            _row = {_row.begin() + _row_length, _row.end() + _row_length};
            return *this;
        }

        [[nodiscard]] constexpr ConstRowIterator operator++(int) noexcept
        {
            ConstRowIterator previous{*this};
            ++*this;
            return previous;
        }

        constexpr ConstRowIterator& operator--() noexcept
        {
            _row = {_row.begin() - _row_length, _row.end() - _row_length};
            return *this;
        }

        [[nodiscard]] constexpr ConstRowIterator operator--(int) noexcept
        {
            ConstRowIterator previous{*this};
            --*this;
            return previous;
        }

        [[nodiscard]] constexpr friend bool operator==(ConstRowIterator const& lhs, ConstRowIterator const& rhs)
        {
            return lhs._row == rhs._row;
        }

    private:
        T* _begin{};
        Row _row;
        size_t _row_length{};
    };

    /// <summary>
    /// ConstRowIterator is a LegacyBidirectionalIterator
    /// https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator
    /// </summary>
    class RowIterator : public ConstRowIterator {
    public:
        using pointer = ConstRowIterator::value_type*;
        using reference = ConstRowIterator::value_type&;

        constexpr RowIterator() = default;

        constexpr RowIterator(T* begin, size_t row_length) :
            ConstRowIterator{begin, row_length} {}

        [[nodiscard]] constexpr reference operator*() const noexcept
        {
            return const_cast<reference>(ConstRowIterator::operator*());
        }

        [[nodiscard]] constexpr pointer operator->() const noexcept
        {
            return const_cast<pointer>(ConstRowIterator::operator->());
        }

        constexpr RowIterator& operator++() noexcept
        {
            ConstRowIterator::operator++();
            return *this;
        }

        [[nodiscard]] constexpr RowIterator operator++(int) noexcept
        {
            RowIterator previous{*this};
            ++*this;
            return previous;
        }

        constexpr RowIterator& operator--() noexcept
        {
            ConstRowIterator::operator--();
            return *this;
        }

        [[nodiscard]] constexpr RowIterator operator--(int) noexcept
        {
            RowIterator previous{*this};
            --*this;
            return previous;
        }
    };

    class ConstRows {
    public:
        using value_type = T;
        using pointer = value_type*;
        using const_pointer = value_type const*;
        using const_iterator = ConstRowIterator;

        constexpr ConstRows() = default;

        constexpr ConstRows(const_pointer begin, const_pointer end, size_t row_length) :
            _begin{const_cast<pointer>(begin)},
            _end{const_cast<pointer>(end)},
            _row_length{row_length}
        {}

        [[nodiscard]] constexpr const_iterator begin() const noexcept { return {_begin, _row_length}; }
        [[nodiscard]] constexpr const_iterator end() const noexcept { return {_end, _row_length}; }
        [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return {_begin, _row_length}; }
        [[nodiscard]] constexpr const_iterator cend() const noexcept { return {_end, _row_length}; }

    protected:
        pointer _begin{};
        pointer _end{};
        size_t _row_length{};
    };

    class Rows : public ConstRows {
    public:
        using iterator = RowIterator;
        using pointer = ConstRows::pointer;

        constexpr Rows() = default;

        constexpr Rows(pointer begin, pointer end, size_t row_length) :
            ConstRows(begin, end, row_length) {}

        [[nodiscard]] constexpr iterator begin() noexcept { return {ConstRows::_begin, ConstRows::_row_length}; }
        [[nodiscard]] constexpr iterator end() noexcept { return {ConstRows::_end, ConstRows::_row_length}; }
    };

    using value_type = T;
    using reference = value_type&;
    using const_reference = value_type const&;
    using iterator = std::vector<T>::iterator;
    using const_iterator = std::vector<T>::const_iterator;
    using difference_type = std::vector<T>::difference_type;
    using size_type = std::vector<T>::size_type;

    constexpr Grid() = default;

    constexpr Grid(size_type width, size_type height) :
        _vector(width * height),
        _width{width},
        _height{height}
    {}

    constexpr Grid(size_type width, size_type height, std::initializer_list<T> il) :
        _vector(width * height),
        _width{width},
        _height{height}
    {
        auto end = il.end() - (il.size() > _vector.size() ? il.size() - _vector.size() : 0);
        std::copy(il.begin(), end, _vector.begin());
    }

    [[nodiscard]] constexpr iterator begin() noexcept { return _vector.begin(); }
    [[nodiscard]] constexpr iterator end() noexcept { return _vector.end(); };
    [[nodiscard]] constexpr const_iterator begin() const noexcept { return _vector.begin(); }
    [[nodiscard]] constexpr const_iterator end() const noexcept { return _vector.end(); };
    [[nodiscard]] constexpr const_iterator cbegin() const noexcept { return _vector.cbegin(); }
    [[nodiscard]] constexpr const_iterator cend() const noexcept { return _vector.cend(); };
    [[nodiscard]] constexpr bool empty() const noexcept { return _vector.empty(); }
    [[nodiscard]] constexpr size_type size() const noexcept { return _vector.size(); }
    [[nodiscard]] constexpr size_type max_size() const noexcept { return _vector.max_size(); }

    [[nodiscard]] constexpr value_type* data() noexcept { return _vector.data(); }
    [[nodiscard]] constexpr value_type const* data() const noexcept { return const_cast<value_type const*>(_vector.data()); }
    [[nodiscard]] constexpr reference operator[](size_type idx) noexcept { return _vector[idx]; }
    [[nodiscard]] constexpr const_reference operator[](size_type idx) const noexcept { return _vector[idx]; }
    [[nodiscard]] constexpr size_type width() const noexcept { return _width; }
    [[nodiscard]] constexpr size_type height() const noexcept { return _height; }

    [[nodiscard]] constexpr friend bool operator==(Grid const& lhs, Grid const& rhs) noexcept
    {
        return lhs.width() == rhs.width() &&
            lhs.height() == rhs.height() &&
            std::equal(lhs.begin(), lhs.end(), rhs.begin());
    }

    constexpr void swap(Grid& right) noexcept
    {
        if(this != &right) {
            using std::swap;
            swap(_vector, right._vector);
            swap(_width, right._width);
            swap(_height, right._height);
        }
    }

    constexpr friend void swap(Grid& left, Grid& right) noexcept
    {
        left.swap(right);
    }

    [[nodiscard]] constexpr Rows EnumerateRows() noexcept
    {
        return {data(), data() + size(), _width};
    }

    [[nodiscard]] constexpr ConstRows EnumerateRows() const noexcept
    {
        return {data(), data() + size(), _width};
    }

private:
    std::vector<T> _vector;
    size_type _width{};
    size_type _height{};
};

}

#endif

Code Source - The tests - grid_test.cpp

Included for completeness only Using the Microsoft CPP test framework.

#include "CppUnitTest.h"

using namespace Microsoft::VisualStudio::CppUnitTestFramework;

#include "../src/grid.hpp"
#include <algorithm>
#include <array>
#include <iterator>
#include <type_traits>
#include <utility>
#include <vector>

using namespace shoujin;

namespace assist {

class GridVectorAsserter {
public:
    GridVectorAsserter(int width, int height) :
        _width{width},
        _height{height},
        _expected{height} {}

    void Push(int row, int value) { _expected[row].push_back(value); }

    template<int Size>
    void AssertSameAsArray(std::array<int, Size> const array)
    {
        _assert_all_rows_same_size();
        _assert_match_array(array);
    }

private:
    int _width, _height;
    std::vector<std::vector<int>> _expected;

    void _assert_all_rows_same_size()
    {
        auto size = _expected[0].size();
        for(auto& row : _expected)
            Assert::AreEqual(size, row.size());
    }

    template<int Size>
    void _assert_match_array(std::array<int, Size> const array)
    {
        for(int i = 0; i < _height; ++i)
            Assert::IsTrue(_compare_row(_expected[i].data(), array.data() + i * _width));
    }

    bool _compare_row(int const* actual, int const* expected)
    {
        bool result = true;
        for(int i = 0; i < _width; ++i, ++actual, ++expected)
            result &= *actual == *expected;
        return result;
    }
};

template<int Size>
std::array<int, Size> CreateIntArray()
{
    std::array<int, Size> array;
    for(int i = 0; i < Size; ++i)
        array[i] = i + 1;
    return array;
}

template<typename T>
Grid<T> CreateFilledGrid(size_t width, size_t height)
{
    Grid<T> grid(width, height);

    auto&& it = grid.data();
    auto&& end = it + grid.size();
    auto i = 0;
    while(it != end)
        *it++ = ++i;

    return grid;
}

}

using T = int;
using Row = Grid<T>::Row;
using ConstRowIterator = Grid<T>::ConstRowIterator;
using RowIterator = Grid<T>::RowIterator;
using Rows = Grid<T>::Rows;
using ConstRows = Grid<T>::ConstRows;

TEST_CLASS(Grid_RowTest) {
    TEST_METHOD(Alias_ValueType_IsType) {
        Assert::IsTrue(std::is_same_v<Row::value_type, int>);
    }

    TEST_METHOD(Alias_Pointer_IsTypePointer) {
        Assert::IsTrue(std::is_same_v<Row::pointer, Row::value_type*>);
    }

    TEST_METHOD(Alias_ConstPointer_IsTypeConstPointer) {
        Assert::IsTrue(std::is_same_v<Row::const_pointer, Row::value_type const*>);
    }

    TEST_METHOD(Alias_Reference_IsTypeReference) {
        Assert::IsTrue(std::is_same_v<Row::reference, Row::value_type&>);
    }

    TEST_METHOD(Alias_ConstReference_IsTypeConstReference) {
        Assert::IsTrue(std::is_same_v<Row::const_reference, Row::value_type const&>);
    }

    TEST_METHOD(Alias_Iterator_IsTypePointer) {
        Assert::IsTrue(std::is_same_v<Row::iterator, Row::pointer>);
    }

    TEST_METHOD(Alias_ConstIterator_IsTypeConstPointer) {
        Assert::IsTrue(std::is_same_v<Row::const_iterator, Row::const_pointer>);
    }

    TEST_METHOD(IsCopyConstructible) {
        Assert::IsTrue(std::is_copy_constructible_v<Row>);
    }

    TEST_METHOD(IsCopyAssignable) {
        Assert::IsTrue(std::is_copy_assignable_v<Row>);
    }

    TEST_METHOD(IsMoveConstructible) {
        Assert::IsTrue(std::is_move_constructible_v<Row>);
    }

    TEST_METHOD(IsMoveAssignable) {
        Assert::IsTrue(std::is_move_assignable_v<Row>);
    }

    TEST_METHOD(IsDefaultConstructible) {
        Assert::IsTrue(std::is_default_constructible_v<Row>);
    }

    TEST_METHOD(Begin_WhenRowIsNotConst_ReturnIteratorType) {
        Row row;

        auto actual = row.begin();

        Assert::IsTrue(std::is_same_v<decltype(actual), Row::iterator>);
    }

    TEST_METHOD(End_WhenRowIsNotConst_ReturnIteratorType) {
        Row row;

        auto actual = row.end();

        Assert::IsTrue(std::is_same_v<decltype(actual), Row::iterator>);
    }

    TEST_METHOD(Begin_WhenRowIsConst_ReturnConstIteratorType) {
        Row const row;

        auto actual = row.begin();

        Assert::IsTrue(std::is_same_v<decltype(actual), Row::const_iterator>);
    }

    TEST_METHOD(End_WhenRowIsConst_ReturnConstIteratorType) {
        Row const row;

        auto actual = row.end();

        Assert::IsTrue(std::is_same_v<decltype(actual), Row::const_iterator>);
    }

    TEST_METHOD(EnumerateUsingRangeFor_EnumerateEachValue) {
        int data[]{1, 2, 3};
        Row row{data, data + 3};
        int i = 0;

        for(auto&& it : row)
            Assert::AreEqual(++i, it);
    }

    TEST_METHOD(EnumerateUsingRangeForConst_EnumerateEachValue) {
        int data[]{1, 2, 3};
        Row row{data, data + 3};
        Row const const_row = std::as_const(row);
        int i = 0;

        for(auto&& it : const_row)
            Assert::AreEqual(++i, it);
    }

    TEST_METHOD(Size_ReturnSize) {
        int data[]{1, 2, 3};
        Row const const_row{data, data + 3};
        Assert::AreEqual<size_t>(3, const_row.size());
    }

    TEST_METHOD(OperatorSubscript_ReturnValue) {
        int data[]{1, 2, 3};
        Row const const_row{data, data + 3};
        Assert::AreEqual(2, const_row[1]);
    }

    TEST_METHOD(OperatorSubscript_WriteToIt_Compiles) {
        int data[]{1, 2, 3};
        Row row{data, data + 3};
        row[1] = 5;
    }

    TEST_METHOD(OperatorSubscriptConst_ReturnValue) {
        int data[]{1, 2, 3};
        Row row{data, data + 3};
        Assert::AreEqual(2, row[1]);
    }

    TEST_METHOD(OperatorComparison_WhenBeginEndMismatch_ReturnFalse) {
        int data1[]{1, 2};
        int data2[]{1, 2};
        Row row1{data1, data1 + 2};
        Row Row{data2, data2 + 2};

        bool actual = row1 == Row;

        Assert::IsFalse(actual);
    }

    TEST_METHOD(OperatorComparison_WhenBeginEndMatch_ReturnTrue) {
        int data[]{1, 2};
        Row row1{data, data + 2};
        Row Row{data, data + 2};

        bool actual = row1 == Row;

        Assert::IsTrue(actual);
    }
};

TEST_CLASS(Grid_ConstRowIteratorTest) {
    TEST_METHOD(Alias_ValueType_IsType) {
        Assert::IsTrue(std::is_same_v<ConstRowIterator::value_type, Row>);
    }

    TEST_METHOD(Alias_Reference_IsTypeReference) {
        Assert::IsTrue(std::is_same_v<ConstRowIterator::reference, ConstRowIterator::value_type const&>);
    }

    TEST_METHOD(Alias_Pointer_IsTypePointer) {
        Assert::IsTrue(std::is_same_v<ConstRowIterator::pointer, ConstRowIterator::value_type const*>);
    }

    TEST_METHOD(Alias_DifferenceType_IsPtrDiffT) {
        Assert::IsTrue(std::is_same_v<ConstRowIterator::difference_type, ptrdiff_t>);
    }

    TEST_METHOD(Alias_IteratorCategory_IsRandomAccessIterator) {
        Assert::IsTrue(std::is_same_v<ConstRowIterator::iterator_category, std::random_access_iterator_tag>);
    }

    TEST_METHOD(IsCopyConstructible) {
        Assert::IsTrue(std::is_copy_constructible_v<ConstRowIterator>);
    }

    TEST_METHOD(IsCopyAssignable) {
        Assert::IsTrue(std::is_copy_assignable_v<ConstRowIterator>);
    }

    TEST_METHOD(IsMoveConstructible) {
        Assert::IsTrue(std::is_move_constructible_v<ConstRowIterator>);
    }

    TEST_METHOD(IsMoveAssignable) {
        Assert::IsTrue(std::is_move_assignable_v<ConstRowIterator>);
    }

    TEST_METHOD(IsDefaultConstructible) {
        Assert::IsTrue(std::is_default_constructible_v<ConstRowIterator>);
    }

    TEST_METHOD(IsDestructible) {
        Assert::IsTrue(std::is_destructible_v<ConstRowIterator>);
    }

    TEST_METHOD(IsSwappable) {
        Assert::IsTrue(std::is_swappable_v<ConstRowIterator>);
    }

    TEST_METHOD(OperatorSubscript_ReturnSpecifiedRow) {
        int data[]{1, 2, 3};
        ConstRowIterator c_row_it{data, 3};

        Row actual = c_row_it[1];

        Assert::AreEqual(1, actual[0]);
    }

    TEST_METHOD(OperatorIndirection_ReturnCurrentRow) {
        int data[]{1, 2, 3};
        ConstRowIterator c_row_it{data, 3};

        Row const actual = *c_row_it;

        Assert::AreEqual(1, *actual.begin());
    }

    TEST_METHOD(OperatorDereference_ReturnCurrentRow) {
        int data[]{1, 2, 3};
        ConstRowIterator c_row_it{data, 3};

        ConstRowIterator::pointer actual = c_row_it.operator->();

        Assert::AreEqual(1, *actual->begin());
    }

    TEST_METHOD(OperatorIncrementPrefix_MoveToNextRowAndReturnIt) {
        int data[]{1, 2, 3, 4};
        ConstRowIterator c_row_it{data, 2};

        ConstRowIterator& actual = ++c_row_it;

        Assert::AreEqual(3, *actual->begin());
    }

    TEST_METHOD(OperatorIncrementPostfix_MoveToNextRowAndReturnOriginalRow) {
        int data[]{1, 2, 3, 4};
        ConstRowIterator c_row_it{data, 2};

        ConstRowIterator actual = c_row_it++;

        Assert::AreEqual(1, *(*actual).begin());
        Assert::AreEqual(3, *c_row_it->begin());
    }

    TEST_METHOD(OperatorIncrementPrefix_WhenUsedOnACopy_DoNotIncrementSource) {
        int data[]{1, 2, 3, 4};
        auto c_row_it = ConstRowIterator{data, 2};
        auto c_row_it_copy = c_row_it;

        ++c_row_it_copy;

        Assert::AreEqual(1, *c_row_it->begin());
        Assert::AreEqual(3, *c_row_it_copy->begin());
    }

    TEST_METHOD(OperatorDecrementPrefix_MoveToPreviousRowAndReturnIt) {
        int data[]{1, 2, 3, 4, 5, 6};
        ConstRowIterator c_row_it{data, 2};
        ++++c_row_it;

        ConstRowIterator& actual = --c_row_it;

        Assert::AreEqual(3, *actual->begin());
    }

    TEST_METHOD(OperatorDecrementPostfix_MoveToPreviousRowAndReturnOriginalRow) {
        int data[]{1, 2, 3, 4, 5, 6};
        ConstRowIterator c_row_it{data, 2};
        ++++c_row_it;

        ConstRowIterator actual = c_row_it--;

        Assert::AreEqual(5, *(*actual).begin());
        Assert::AreEqual(3, *c_row_it->begin());
    }

    TEST_METHOD(OperatorComparison_WhenReferencingOtherRow_ReturnFalse) {
        int data1[]{1, 2};
        int data2[]{1, 2};
        ConstRowIterator c_row_it1{data1, 2};
        ConstRowIterator c_row_it2{data2, 2};

        bool actual = c_row_it1 == c_row_it2;

        Assert::IsFalse(actual);
    }

    TEST_METHOD(OperatorComparison_WhenReferencingSameRowMatch_ReturnTrue) {
        int data[]{1, 2};
        ConstRowIterator c_row_it1{data, 2};
        ConstRowIterator c_row_it2{data, 2};

        bool actual = c_row_it1 == c_row_it2;

        Assert::IsTrue(actual);
    }
};

TEST_CLASS(Grid_RowIteratorTest) {
    TEST_METHOD(Alias_ValueType_IsType) {
        Assert::IsTrue(std::is_same_v<RowIterator::value_type, Row>);
    }

    TEST_METHOD(Alias_Reference_IsTypeReference) {
        Assert::IsTrue(std::is_same_v<RowIterator::reference, RowIterator::value_type&>);
    }

    TEST_METHOD(Alias_Pointer_IsTypePointer) {
        Assert::IsTrue(std::is_same_v<RowIterator::pointer, RowIterator::value_type*>);
    }

    TEST_METHOD(Alias_DifferenceType_IsPtrDiffT) {
        Assert::IsTrue(std::is_same_v<RowIterator::difference_type, ptrdiff_t>);
    }

    TEST_METHOD(Alias_IteratorCategory_IsRandomAccessIterator) {
        Assert::IsTrue(std::is_same_v<RowIterator::iterator_category, std::random_access_iterator_tag>);
    }

    TEST_METHOD(IsCopyConstructible) {
        Assert::IsTrue(std::is_copy_constructible_v<RowIterator>);
    }

    TEST_METHOD(IsCopyAssignable) {
        Assert::IsTrue(std::is_copy_assignable_v<RowIterator>);
    }

    TEST_METHOD(IsMoveConstructible) {
        Assert::IsTrue(std::is_move_constructible_v<RowIterator>);
    }

    TEST_METHOD(IsMoveAssignable) {
        Assert::IsTrue(std::is_move_assignable_v<RowIterator>);
    }

    TEST_METHOD(IsDefaultConstructible) {
        Assert::IsTrue(std::is_default_constructible_v<RowIterator>);
    }

    TEST_METHOD(IsDestructible) {
        Assert::IsTrue(std::is_destructible_v<RowIterator>);
    }

    TEST_METHOD(IsSwappable) {
        Assert::IsTrue(std::is_swappable_v<RowIterator>);
    }

    TEST_METHOD(OperatorSubscript_ReturnSpecifiedRow) {
        int data[]{1, 2, 3};
        RowIterator row_it{data, 3};

        Row actual = row_it[1];

        Assert::AreEqual(1, actual[0]);
    }

    TEST_METHOD(OperatorIndirection_ReturnCurrentRow) {
        int data[]{1, 2, 3};
        RowIterator row_it{data, 3};
        Row& actual = *row_it;
    }

    TEST_METHOD(OperatorDereference_ReturnCurrentRow) {
        int data[]{1, 2, 3};
        RowIterator row_it{data, 3};

        Row* actual = row_it.operator->();

        Assert::AreEqual(1, *actual->begin());
    }

    TEST_METHOD(OperatorIncrementPrefix_MoveToNextRowAndReturnIt) {
        int data[]{1, 2, 3, 4};
        RowIterator row_it{data, 2};

        RowIterator& actual = ++row_it;

        Assert::AreEqual(3, *actual->begin());
    }

    TEST_METHOD(OperatorIncrementPostfix_MoveToNextRowAndReturnOriginalRow) {
        int data[]{1, 2, 3, 4};
        RowIterator row_it{data, 2};

        RowIterator actual = row_it++;

        Assert::AreEqual(1, *(*actual).begin());
        Assert::AreEqual(3, *row_it->begin());
    }

    TEST_METHOD(OperatorIncrementPrefix_WhenUsedOnACopy_DoNotIncrementSource) {
        int data[]{1, 2, 3, 4};
        auto row_it = RowIterator{data, 2};
        auto row_it_copy = row_it;

        ++row_it_copy;

        Assert::AreEqual(1, *row_it->begin());
        Assert::AreEqual(3, *row_it_copy->begin());
    }

    TEST_METHOD(OperatorDecrementPrefix_MoveToPreviousRowAndReturnIt) {
        int data[]{1, 2, 3, 4, 5, 6};
        RowIterator row_it{data, 2};
        ++++row_it;

        RowIterator& actual = --row_it;

        Assert::AreEqual(3, *actual->begin());
    }

    TEST_METHOD(OperatorDecrementPostfix_MoveToPreviousRowAndReturnOriginalRow) {
        int data[]{1, 2, 3, 4, 5, 6};
        RowIterator row_it{data, 2};
        ++++row_it;

        RowIterator actual = row_it--;

        Assert::AreEqual(5, *(*actual).begin());
        Assert::AreEqual(3, *row_it->begin());
    }

    TEST_METHOD(OperatorComparison_WhenReferencingOtherRow_ReturnFalse) {
        int data1[]{1, 2};
        int data2[]{1, 2};
        RowIterator row_it1{data1, 2};
        RowIterator row_it2{data2, 2};

        bool actual = row_it1 == row_it2;

        Assert::IsFalse(actual);
    }

    TEST_METHOD(OperatorComparison_WhenReferencingSameRowMatch_ReturnTrue) {
        int data[]{1, 2};
        RowIterator row_it1{data, 2};
        RowIterator row_it2{data, 2};

        bool actual = row_it1 == row_it2;

        Assert::IsTrue(actual);
    }
};

TEST_CLASS(Grid_ConstRowsTest) {
    TEST_METHOD(IsCopyConstructible) {
        Assert::IsTrue(std::is_copy_constructible_v<ConstRows>);
    }

    TEST_METHOD(IsCopyAssignable) {
        Assert::IsTrue(std::is_copy_assignable_v<ConstRows>);
    }

    TEST_METHOD(IsMoveConstructible) {
        Assert::IsTrue(std::is_move_constructible_v<ConstRows>);
    }

    TEST_METHOD(IsMoveAssignable) {
        Assert::IsTrue(std::is_move_assignable_v<ConstRows>);
    }

    TEST_METHOD(IsDefaultConstructible) {
        Assert::IsTrue(std::is_default_constructible_v<ConstRows>);
    }

    TEST_METHOD(Begin_ReturnConstIteratorType) {
        ConstRows const_rows;

        auto actual = const_rows.begin();

        Assert::IsTrue(std::is_same_v<decltype(actual), ConstRows::const_iterator>);
    }

    TEST_METHOD(End_ReturnConstIteratorType) {
        ConstRows const_rows;

        auto actual = const_rows.end();

        Assert::IsTrue(std::is_same_v<decltype(actual), ConstRows::const_iterator>);
    }

    TEST_METHOD(EnumerateUsingRangeFor_EnumerateEachRow) {
        //Arrange
        constexpr auto kWidth = 3;
        constexpr auto kHeight = 2;
        constexpr auto kSize = kWidth * kHeight;
        auto test_data = assist::CreateIntArray<kSize>();
        ConstRows const_rows{test_data.data(), test_data.data() + kSize, kWidth};
        assist::GridVectorAsserter grid_vec{kWidth, kHeight};

        //Act
        for(int y{}; auto&& row : const_rows) {
            for(auto&& cell : row)
                grid_vec.Push(y, cell);
            ++y;
        }

        //Assert
        grid_vec.AssertSameAsArray(test_data);
    }
};

TEST_CLASS(Grid_RowsTest) {
    TEST_METHOD(IsCopyConstructible) {
        Assert::IsTrue(std::is_copy_constructible_v<Rows>);
    }

    TEST_METHOD(IsCopyAssignable) {
        Assert::IsTrue(std::is_copy_assignable_v<Rows>);
    }

    TEST_METHOD(IsMoveConstructible) {
        Assert::IsTrue(std::is_move_constructible_v<Rows>);
    }

    TEST_METHOD(IsMoveAssignable) {
        Assert::IsTrue(std::is_move_assignable_v<Rows>);
    }

    TEST_METHOD(IsDefaultConstructible) {
        Assert::IsTrue(std::is_default_constructible_v<Rows>);
    }

    TEST_METHOD(Begin_ReturnIteratorType) {
        Rows rows;

        auto actual = rows.begin();

        Assert::IsTrue(std::is_same_v<decltype(actual), Rows::iterator>);
    }

    TEST_METHOD(End_ReturnIteratorType) {
        Rows rows;

        auto actual = rows.end();

        Assert::IsTrue(std::is_same_v<decltype(actual), Rows::iterator>);
    }

    TEST_METHOD(EnumerateUsingRangeFor_EnumerateEachRow) {
        //Arrange
        constexpr auto kWidth = 3;
        constexpr auto kHeight = 2;
        constexpr auto kSize = kWidth * kHeight;
        auto test_data = assist::CreateIntArray<kSize>();
        Rows rows{test_data.data(), test_data.data() + kSize, kWidth};
        assist::GridVectorAsserter grid_vec{kWidth, kHeight};

        //Act
        for(int y{}; auto&& row : rows) {
            for(auto&& cell : row)
                grid_vec.Push(y, cell);
            ++y;
        }

        //Assert
        grid_vec.AssertSameAsArray(test_data);
    }

    TEST_METHOD(EnumerateUsingBeginEnd_EnumerateEachRow) {
        //Arrange
        constexpr auto kWidth = 3;
        constexpr auto kHeight = 2;
        constexpr auto kSize = kWidth * kHeight;
        auto test_data = assist::CreateIntArray<kSize>();
        auto rows = Rows{test_data.data(), test_data.data() + kSize, kWidth};
        assist::GridVectorAsserter grid_vec{kWidth, kHeight};

        //Act
        RowIterator row_begin = rows.begin();
        RowIterator row_end = rows.end();
        RowIterator row_it = row_begin;
        auto y = int{};
        while(row_it != row_end) {
            Row& row = row_it.operator*();
            int* cell_begin = row.begin();
            int* cell_end = row.end();
            int* cell_it = cell_begin;
            while(cell_it != cell_end) {
                int& cell = *cell_it;
                grid_vec.Push(y, cell);
                ++cell_it;
            }
            ++row_it, ++y;
        }

        //Assert
        grid_vec.AssertSameAsArray(test_data);
    }
};

TEST_CLASS(Grid_GridTest) {
public:
    TEST_METHOD(Alias_ValueType_IsType) {
        Assert::IsTrue(std::is_same_v<Grid<T>::value_type, int>);
    }

    TEST_METHOD(Alias_Reference_IsTypeReference) {
        Assert::IsTrue(std::is_same_v<Grid<T>::reference, int&>);
    }

    TEST_METHOD(Alias_ConstReference_IsConstTypeRef) {
        Assert::IsTrue(std::is_same_v<Grid<T>::const_reference, int const&>);
    }

    TEST_METHOD(Alias_Iterator_IsVectorTypeIterator) {
        Assert::IsTrue(std::is_same_v<Grid<T>::iterator, std::vector<int>::iterator>);
    }

    TEST_METHOD(Alias_ConstIterator_IsVectorTypeConstIterator) {
        Assert::IsTrue(std::is_same_v<Grid<T>::const_iterator, std::vector<int>::const_iterator>);
    }

    TEST_METHOD(Alias_DifferenceType_IsVectorTypeDifferenceType) {
        Assert::IsTrue(std::is_same_v<Grid<T>::difference_type, std::vector<int>::difference_type>);
    }

    TEST_METHOD(Alias_SizeType_IsVectorTypeSizeType) {
        Assert::IsTrue(std::is_same_v<Grid<T>::size_type, std::vector<int>::size_type>);
    }

    TEST_METHOD(IsDefaultConstructible) {
        Assert::IsTrue(std::is_default_constructible_v<Grid<T>>);
    }

    TEST_METHOD(IsCopyConstructible) {
        Assert::IsTrue(std::is_copy_constructible_v<Grid<T>>);
    }

    TEST_METHOD(IsCopyAssignable) {
        Assert::IsTrue(std::is_copy_assignable_v<Grid<T>>);
    }

    TEST_METHOD(IsMoveConstructible) {
        Assert::IsTrue(std::is_move_constructible_v<Grid<T>>);
    }

    TEST_METHOD(IsMoveAssignable) {
        Assert::IsTrue(std::is_move_assignable_v<Grid<T>>);
    }

    TEST_METHOD(IsDestructible) {
        Assert::IsTrue(std::is_destructible_v<Grid<T>>);
    }

    TEST_METHOD(Width_ReturnWidth) {
        Grid<T> grid(5, 3);
        Assert::AreEqual<size_t>(5, grid.width());
    }

    TEST_METHOD(Height_ReturnHeight) {
        Grid<T> grid(5, 3);
        Assert::AreEqual<size_t>(3, grid.height());
    }

    TEST_METHOD(Size_ReturnWidthTimesHeight) {
        Grid<T> grid(5, 3);
        Assert::AreEqual<size_t>(15, grid.size());
    }

    TEST_METHOD(MaxSize_GreaterThanOne) {
        Grid<T> grid(5, 3);

        Grid<T>::size_type actual = grid.max_size();

        Assert::IsTrue(actual > 0);
    }

    TEST_METHOD(Data_ReturnSameAsBegin) {
        Grid<T> grid(5, 3);
        Assert::IsTrue(*grid.data() == *grid.begin());
    }

    TEST_METHOD(DataConst_ReturnSameAsBegin) {
        Grid<T> grid(5, 3);
        Grid<T> const& const_grid = std::as_const(grid);
        Assert::IsTrue(*const_grid.data() == *const_grid.begin());
    }

    TEST_METHOD(Empty_GridIsEmpty_ReturnTrue) {
        Grid<T> grid;
        Assert::IsTrue(grid.empty());
    }

    TEST_METHOD(Empty_GridIsNotEmpty_ReturnFalse) {
        Grid<T> grid(3, 3);
        Assert::IsFalse(grid.empty());
    }

    TEST_METHOD(OperatorSubscript_ReturnValue) {
        Grid<T> grid(2, 2);
        *(grid.data() + 2) = 53;

        int& actual = grid[2];

        Assert::AreEqual(53, actual);
    }

    TEST_METHOD(OperatorSubscript_WriteToIt_Compiles) {
        Grid<T> grid(2, 2);
        grid[2] = 50;
        Assert::AreEqual(50, grid[2]);
    }

    TEST_METHOD(OperatorSubscriptConst_ReturnValue) {
        Grid<T> grid(2, 2);
        *(grid.data() + 2) = 53;
        Grid<T> const& const_grid = std::as_const(grid);

        int const& actual = const_grid[2];

        Assert::AreEqual(53, actual);
    }

    TEST_METHOD(BeginEnd_DifferenceIsSameAsSize) {
        Grid<T> grid(5, 3);
        ptrdiff_t diff = grid.end() - grid.begin();
        Assert::IsTrue(diff == grid.size());
    }

    TEST_METHOD(Begin_WhenGridIsNotConst_ReturnIteratorType) {
        Grid<T> grid;

        auto actual = grid.begin();

        Assert::IsTrue(std::is_same_v<decltype(actual), Grid<T>::iterator>);
    }

    TEST_METHOD(End_WhenGridIsNotConst_ReturnIteratorType) {
        Grid<T> grid;

        auto actual = grid.end();

        Assert::IsTrue(std::is_same_v<decltype(actual), Grid<T>::iterator>);
    }

    TEST_METHOD(Begin_WhenGridIsConst_ReturnConstIteratorType) {
        Grid<T> const grid;

        auto actual = grid.begin();

        Assert::IsTrue(std::is_same_v<decltype(actual), Grid<T>::const_iterator>);
    }

    TEST_METHOD(End_WhenGridIsConst_ReturnConstIteratorType) {
        Grid<T> const grid;

        auto actual = grid.end();

        Assert::IsTrue(std::is_same_v<decltype(actual), Grid<T>::const_iterator>);
    }

    TEST_METHOD(Begin_ReturnIteratorType) {
        Grid<T> grid;

        auto actual = grid.begin();

        Assert::IsTrue(std::is_same_v<decltype(actual), Grid<T>::iterator>);
    }

    TEST_METHOD(End_ReturnIteratorType) {
        Grid<T> grid;

        auto actual = grid.end();

        Assert::IsTrue(std::is_same_v<decltype(actual), Grid<T>::iterator>);
    }

    TEST_METHOD(EnumerateUsingRangeFor_EnumerateEachValue) {
        auto grid = assist::CreateFilledGrid<T>(5, 3);
        int i = 0;

        for(auto&& it : grid)
            Assert::AreEqual(++i, it);
    }

    TEST_METHOD(EnumerateUsingRangeForConst_EnumerateEachValue) {
        auto const grid = assist::CreateFilledGrid<T>(5, 3);
        int i = 0;

        for(auto&& it : grid)
            Assert::AreEqual(++i, it);
    }

    TEST_METHOD(OperatorComparison_WhenSizeMismatch_ReturnFalse) {
        auto a = assist::CreateFilledGrid<T>(5, 3);
        auto b = assist::CreateFilledGrid<T>(3, 5);

        Assert::IsFalse(a == b);
        Assert::IsTrue(a != b);
    }

    TEST_METHOD(OperatorComparison_WhenDataMismatch_ReturnFalse) {
        auto a = assist::CreateFilledGrid<T>(2, 3);
        auto b = Grid<T>(2, 3);

        Assert::IsFalse(a == b);
        Assert::IsTrue(a != b);
    }

    TEST_METHOD(OperatorComparison_WhenDataMatch_ReturnTrue) {
        auto a = assist::CreateFilledGrid<T>(2, 3);
        auto b = assist::CreateFilledGrid<T>(2, 3);

        Assert::IsTrue(a == b);
        Assert::IsFalse(a != b);
    }

    TEST_METHOD(Swap_MemberFunction_Swapped) {
        Grid<T> a(2, 3);
        Grid<T> b(3, 2);

        a.swap(b);

        Assert::AreEqual<size_t>(3, a.width());
        Assert::AreEqual<size_t>(2, b.width());
    }

    TEST_METHOD(Swap_GlobalFunction_Swapped) {
        Grid<T> a(2, 3);
        Grid<T> b(3, 2);

        swap(a, b);

        Assert::AreEqual<size_t>(3, a.width());
        Assert::AreEqual<size_t>(2, b.width());
    }

    TEST_METHOD(Initializer_Width_ReturnWidth) {
        Grid<T> grid{1, 2, {}};
        Assert::AreEqual<size_t>(1, grid.width());
    }

    TEST_METHOD(Initializer_Height_ReturnHeight) {
        Grid<T> grid{1, 2, {}};
        Assert::AreEqual<size_t>(2, grid.height());
    }

    TEST_METHOD(Initializer_Empty_AllItemsHaveDefaultValue) {
        Grid<T> grid{1, 2, {}};
        Assert::IsTrue(std::all_of(grid.begin(), grid.end(), [](int i) { return i == 0; }));
    }

    TEST_METHOD(Initializer_AllItemsInitialized) {
        auto values = {4, 3, 2, 6, 4, 1};

        Grid<T> grid{3, 2, values};

        Assert::IsTrue(std::equal(grid.begin(), grid.end(), values.begin()));
    }

    TEST_METHOD(Initializer_TooManyItems_ExtraItemsIgnored) {
        Grid<T> grid{1, 2, {4, 5, 6, 7}};

        auto expected = {4, 5, 6};
        Assert::IsTrue(std::equal(grid.begin(), grid.end(), expected.begin()));
    }

    TEST_METHOD(Initializer_NotEnoughItems_RemainingItemsHaveDefaultValue) {
        Grid<T> grid{1, 2, {1, 2}};

        auto expected = {1, 2, 0, 0};
        Assert::IsTrue(std::equal(grid.begin(), grid.end(), expected.begin()));
    }

    TEST_METHOD(EnumerateRows_EnumerateUsingRangeFor_EnumerateEachRow) {
        //Arrange
        constexpr auto kWidth = 3;
        constexpr auto kHeight = 2;
        auto test_data = assist::CreateIntArray<kWidth * kHeight>();
        Grid<T> grid = assist::CreateFilledGrid<T>(kWidth, kHeight);
        assist::GridVectorAsserter grid_vec{kWidth, kHeight};

        //Act
        for(int y{}; Row & row : grid.EnumerateRows()) {
            for(int& cell : row)
                grid_vec.Push(y, cell);
            ++y;
        }

        //Assert
        grid_vec.AssertSameAsArray(test_data);
    }

    TEST_METHOD(EnumerateRows_EnumerateUsingRangeForConst_EnumerateEachRow) {
        //Arrange
        constexpr auto kWidth = 3;
        constexpr auto kHeight = 2;
        auto test_data = assist::CreateIntArray<kWidth * kHeight>();
        Grid<T> const const_grid = assist::CreateFilledGrid<T>(kWidth, kHeight);
        assist::GridVectorAsserter grid_vec{kWidth, kHeight};

        //Act
        for(int y{}; auto&& row : const_grid.EnumerateRows()) {
            for(auto&& cell : row)
                grid_vec.Push(y, cell);
            ++y;
        }

        //Assert
        grid_vec.AssertSameAsArray(test_data);
    }
};

What's next

After applying any improvements coming from this review, Grid will be done and will be used to implement a color control in a GUI, similar to the HTML5 color picker.

Thank you to everyone spending time on this post.
Althrough it is not needed for the code review, here is a link to the grid.
https://github.com/ShigotoShoujin/shigoto.shoujin/blob/dev/src/grid.hpp

\$\endgroup\$
2
  • 1
    \$\begingroup\$ You have a lot of [[nodiscard]] constexpr and const noexcept going on. Why? \$\endgroup\$
    – Mast
    Commented Dec 19, 2021 at 18:11
  • 1
    \$\begingroup\$ [[nodiscard]] to warn when there is no point to call the function is the return value is going to be ignored. constexpr to make the container work in the context of the constexpr. const when a member function should not change the class state. And noexcept when the function will clearly never throw. Its verbose but so what? :) \$\endgroup\$ Commented Dec 19, 2021 at 19:04

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.