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.