7
\$\begingroup\$

I invested an hour or so to reimplement the C++ header <array>. Most stuff is in the namespace my_std, but tuple_size, etc. are not, otherwise they are useless. I aimed for C++14.

One caveat: the array<T, N>::swap member function's noexcept specification is too complicated, and I chose not to reimplement the std::is_nothrow_swappable trait which is not available prior to C++17.

I used cppreference as a reference. I did not check everything, though, and there may be nonconforming stuff or stuff taken from C++17. Feel free to tell me :)

Here is my code, within 300 lines: (excluding blank lines and comments)

// array.hpp
// C++14 std::array implementation

#ifndef INC_ARRAY_HPP_JCr9Lp1ED0
#define INC_ARRAY_HPP_JCr9Lp1ED0

#include <algorithm>
#include <cstddef>
#include <initializer_list>
#include <iterator>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>

namespace my_std {

    template <class T, std::size_t N>
    struct array {
    private:
        void error() const
        {
            throw std::out_of_range{ "array out of range" };
        }

    public:
        using value_type = T;
        using size_type = std::size_t;
        using difference_type = std::ptrdiff_t;
        using reference = T&;
        using const_reference = const T&;
        using pointer = T*;
        using const_pointer = const T*;
        using iterator = T*;
        using const_iterator = const T*;
        using reverse_iterator = std::reverse_iterator<iterator>;
        using const_reverse_iterator = std::reverse_iterator<const_iterator>;

        constexpr bool empty() const noexcept
        {
            return N == 0;
        }
        constexpr size_type size() const noexcept
        {
            return N;
        }
        constexpr size_type max_size() const noexcept
        {
            return N;
        }

        T& at(std::size_t pos)
        {
            if (pos >= N)
                this->error();
            return elems[pos];
        }
        constexpr const T& at(std::size_t pos) const
        {
            if (pos >= N)
                this->error();
            return elems[pos];
        }
        T& operator[](std::size_t pos)
        {
            return elems[pos];
        }
        constexpr const T& operator[](std::size_t pos) const
        {
            return elems[pos];
        }

        T& front()
        {
            return elems[0];
        }
        constexpr const T& front() const
        {
            return elems[0];
        }
        T& back()
        {
            return elems[N - 1];
        }
        constexpr const T& back() const
        {
            return elems[N - 1];
        }

        T* data() noexcept
        {
            return elems;
        }
        constexpr const T* data() const noexcept
        {
            return elems;
        }

        T* begin() noexcept
        {
            return elems;
        }
        const T* begin() const noexcept
        {
            return elems;
        }
        const T* cbegin() const noexcept
        {
            return elems;
        }

        T* end() noexcept
        {
            return begin() + N;
        }
        const T* end() const noexcept
        {
            return begin() + N;
        }
        const T* cend() const noexcept
        {
            return begin() + N;
        }

        auto rbegin() noexcept
        {
            return std::make_reverse_iterator(end());
        }
        auto rbegin() const noexcept
        {
            return std::make_reverse_iterator(end());
        }
        auto crbegin() 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());
        }
        auto crend() const noexcept
        {
            return std::make_reverse_iterator(begin());
        }

        void fill(const T& value)
        {
            std::fill_n(elems, N, value);
        }

        /*
        Note: is_nothrow_swappable_v
        is not available prior to C++17.
        I am not going to the trouble to
        implement it from scratch.
        So the noexcept specification of swap
        is left unimplemented.
        */
        void swap(array& other) /*noexcept(std::is_swappable_v<T>)*/
        {
            std::swap_ranges(begin(), end(), other.begin());
        }

        T elems[N];
    };

    template <class T>
    struct array<T, 0> {
    private:
        void error() const
        {
            throw std::out_of_range{ "array out of range" };
        }

    public:
        using value_type = T;
        using size_type = std::size_t;
        using difference_type = std::ptrdiff_t;
        using reference = T&;
        using const_reference = const T&;
        using pointer = T*;
        using const_pointer = const T*;
        using iterator = T*;
        using const_iterator = const T*;
        using reverse_iterator = std::reverse_iterator<iterator>;
        using const_reverse_iterator = std::reverse_iterator<const_iterator>;

        constexpr bool           empty() const noexcept { return true; }
        constexpr std::size_t     size() const noexcept { return 0; }
        constexpr std::size_t max_size() const noexcept { return 0; }

                        T&         at(std::size_t pos) { this->error(); }
        constexpr const T&         at(std::size_t pos) const { this->error(); }
                        T& operator[](std::size_t pos) { this->error(); }
        constexpr const T& operator[](std::size_t pos) const { this->error(); }

                        T& front()                { this->error(); }
        constexpr const T& front() const          { this->error(); }
                        T&  back()                { this->error(); }
        constexpr const T&  back() const          { this->error(); }
                        T*  data()       noexcept { return nullptr; }
        constexpr const T*  data() const noexcept { return nullptr; }

              T*   begin()       noexcept { return nullptr; }
        const T*   begin() const noexcept { return nullptr; }
        const T*  cbegin() const noexcept { return nullptr; }

              T*     end()       noexcept { return nullptr; }
        const T*     end() const noexcept { return nullptr; }
        const T*    cend() const noexcept { return nullptr; }

              T*  rbegin()       noexcept { return nullptr; }
        const T*  rbegin() const noexcept { return nullptr; }
        const T* crbegin() const noexcept { return nullptr; }

              T*    rend()       noexcept { return nullptr; }
        const T*    rend() const noexcept { return nullptr; }
        const T*   crend() const noexcept { return nullptr; }

        void fill(const T& value) {}
        void swap(array& other) noexcept {}
    };

    template <class T, std::size_t N>
    inline bool operator==(const array<T, N>& lhs,
                           const array<T, N>& rhs)
    {
        return std::equal(lhs.begin(), lhs.end(),
                          rhs.begin(), rhs.end());
    }
    template <class T, std::size_t N>
    inline bool operator!=(const array<T, N>& lhs,
                           const array<T, N>& rhs)
    {
        return !(lhs == rhs);
    }
    template <class T, std::size_t N>
    inline bool operator< (const array<T, N>& lhs,
                           const array<T, N>& rhs)
    {
        return std::lexicographical_compare(lhs.begin(), lhs.end(),
                                            rhs.begin(), rhs.end());
    }
    template <class T, std::size_t N>
    inline bool operator<=(const array<T, N>& lhs,
                           const array<T, N>& rhs)
    {
        return !(rhs < lhs);
    }
    template <class T, std::size_t N>
    inline bool operator> (const array<T, N>& lhs,
                           const array<T, N>& rhs)
    {
        return rhs < lhs;
    }
    template <class T, std::size_t N>
    inline bool operator>=(const array<T, N>& lhs,
                           const array<T, N>& rhs)
    {
        return !(lhs < rhs);
    }

    template <std::size_t I, class T, std::size_t N>
    constexpr T& get(array<T, N>& a) noexcept
    {
        return a[I];
    }
    template <std::size_t I, class T, std::size_t N>
    constexpr T&& get(array<T, N>&& a) noexcept
    {
        return static_cast<T&&>(a[I]);
    }
    template <std::size_t I, class T, std::size_t N>
    constexpr const T& get(const array<T, N>& a) noexcept
    {
        return a[I];
    }

    template <class T, std::size_t N>
    void swap(array<T, N>& lhs, array<T, N>& rhs)
        noexcept(noexcept(lhs.swap(rhs)))
    {
        return lhs.swap(rhs);
    }
}

namespace std {
    template <class T, std::size_t N>
    class tuple_size<my_std::array<T, N>>
        :public std::integral_constant<std::size_t, N> {
    };
    template <std::size_t I, class T, std::size_t N>
    struct tuple_element<I, my_std::array<T, N>> {
        using type = T;
    };
}

#endif

The naming for the include guard just contains a random string to avoid name clashes. Generally, I don't like (C++) macros.

\$\endgroup\$
7
\$\begingroup\$

If you define all the types why not use them?

    const T* begin() const noexcept

    // More informative to write:

    const_iterator begin() const noexcept
\$\endgroup\$
  • \$\begingroup\$ Good point! I ought to have used the type aliases I have defined to improve readability instead of littering with “magic types.” I accepted this, but other suggestions are also welcomed ;) \$\endgroup\$ – L. F. Feb 14 at 23:40
  • \$\begingroup\$ If you look at my other reviews you will see I usually take code apart piece by piece. But don't see anything else worth commenting on. But it is a very simple bit of code. ;-) \$\endgroup\$ – Martin York Feb 15 at 1:48
4
\$\begingroup\$

One caveat: the array<T, N>::swap member function's noexcept specification is too complicated, and I chose not to reimplement the std::is_nothrow_swappable trait which is not available prior to C++17.

If a type trait doesn't exist, write your own. C++11/14 overlooked a lot of library features that didn't require language support when they were added in 17. is_nothrow_swappable is one of those library features (is also 3 simple structs to test).

I used cppreference as a reference. I did not check everything, though, and there may be nonconforming stuff or stuff taken from C++17. Feel free to tell me :)

You should use the C++14 standard or a draft version close to the final C++14 standard. N4140 was the first draft after C++14 was published.


        void error() const
        {
            throw std::out_of_range{ "array out of range" };
        }

Your function could be better named here. I'd even consider generalizing it to take any const char* message and make it a free function.


        constexpr bool empty() const noexcept
        {
            return N == 0;
        }

Since you specialize the case where N is \$0\$, this function will always return false.


\$\endgroup\$
  • \$\begingroup\$ Thank you! I was just too lazy to implement the is_nothrow_swappable. About <initializer_list>: that's required by the standard. \$\endgroup\$ – L. F. Feb 15 at 3:58
  • \$\begingroup\$ Are you confusing the initializer clauses of an aggregate initializer list (array a = { initializer list }) with std::initializer_list? \$\endgroup\$ – Snowhawk Feb 15 at 4:14
  • 1
    \$\begingroup\$ I know that std::initializer_list is not necessary. But the standard requires that <array> includes <initializer_list>. \$\endgroup\$ – L. F. Feb 15 at 4:15
  • 1
    \$\begingroup\$ Look: eel.is/c++draft/array.syn \$\endgroup\$ – L. F. Feb 15 at 4:16
  • 1
    \$\begingroup\$ You can find a list of publicly available versions of the draft standard here: stackoverflow.com/a/4653479/14065 \$\endgroup\$ – Martin York Feb 15 at 17:34
1
\$\begingroup\$

Another point: instead of

if (pos >= N)
    this->error();

it is sufficient to do

if (pos >= N)
    error();

since the this-> is redundant.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.