I'm trying to replicate std::array
but with modifications that I should put the private data member to the public so the class template fixed_array
will be a structural type that will be qualified as a non-type template parameter.
My goal here is to use the string literals and brace-init list as non-type template parameters where the ISO C++ standard may forbid it, so I created a wrapper for them to call the constructor that has corresponding types.
From class template fixed_string
, I inherited the base class with more specialized member functions.
I applied some C++ 20 features which include string literal operator template, relaxed non-type template parameters, requires clause, spaceship operator, and ranges.
Note: From the definition of UDL operator""fs
, I intended to use this even though ISO warns to declare with no starting underscores because, on my IDE (Code::Blocks), the syntax highlighting will disable the string literal color once I put an underscore.
// headers needed
#include <iterator>
#include <algorithm> // copy_n, fill_n
#include <type_traits>
#include <stdexcept> // out_of_range
#include <tuple>
#include <iostream>
#include <ranges>
#include <numeric>
// class template
template <typename T, size_t N>
struct fixed_array {
// member type alias
using value_type = T;
using size_type = size_t;
using difference_type = ptrdiff_t;
using pointer = value_type*;
using const_pointer = const value_type*;
using reference = value_type&;
using const_reference = const value_type&;
using iterator = const value_type*;
using const_iterator = const value_type*;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
// make underlying data public
value_type data[N];
// constructors
constexpr fixed_array() noexcept {
std::fill_n(data, N, 0);
}
constexpr fixed_array(const value_type& fill_arg) noexcept {
std::fill_n(data, N, fill_arg);
}
constexpr fixed_array(const value_type(&arg)[N]) {
std::copy_n(arg, N, data);
}
constexpr fixed_array(const std::initializer_list<value_type>& args) noexcept {
std::copy_n(args.begin(), N, data);
}
constexpr fixed_array(const fixed_array& other) : fixed_array(other.data) {}
constexpr fixed_array(fixed_array&& other) : fixed_array(std::move(other.data)) {}
// destructors
constexpr ~fixed_array() {}
// assignment
constexpr fixed_array& operator=(const fixed_array& other) {
std::copy_n(other.data, N, data);
return *this;
}
constexpr fixed_array& operator=(fixed_array&& other) {
std::copy_n(std::move(other).data, N, data);
return *this;
}
constexpr fixed_array& operator=(const value_type(&arg)[N]) {
std::copy_n(arg, N, data);
return *this;
}
constexpr fixed_array& operator=(const std::initializer_list<value_type> args) {
std::copy_n(args.begin(), N, data);
return *this;
}
// iterator access
constexpr auto begin() const noexcept { return iterator{data}; }
constexpr auto end() const noexcept { return iterator{data + N}; }
constexpr auto cbegin() const noexcept { return const_iterator{data}; }
constexpr auto cend() const noexcept { return const_iterator{data + N}; }
constexpr auto rbegin() noexcept { return reverse_iterator{data}; }
constexpr auto rend() noexcept { return reverse_iterator{data + N}; }
constexpr auto crbegin() const noexcept { return const_reverse_iterator{data}; }
constexpr auto crend() const noexcept { return const_reverse_iterator{data + N}; }
// capacity
constexpr size_type size() const noexcept { return N; }
constexpr size_type max_size() const noexcept { return N; }
// element access
constexpr reference operator[](size_type pos) { return data[pos]; }
constexpr const_reference operator[](size_type pos) const { return data[pos]; }
constexpr reference at(size_type pos) {
if (!(pos < N)) {
throw std::out_of_range("indexing request is out of range...");
} else {
return data[pos];
}
}
constexpr const_reference at(size_type pos) const {
if (!(pos < N)) {
throw std::out_of_range("indexing request is out of range...");
} else {
return data[pos];
}
}
constexpr reference front() { return data[0]; }
constexpr const_reference front() const { return data[0]; }
constexpr reference back() { return data[N - 1]; }
constexpr const_reference back() const { return data[N - 1]; }
[[nodiscard]] constexpr bool empty() const { return N == 0; }
// equality
constexpr bool operator==(const fixed_array& other) const {
return std::equal(begin(), end(), other.begin());
}
constexpr auto operator<=>(const fixed_array& other) const = default;
// operations
constexpr void fill(const value_type& value) {
std::fill_n(data, N, value);
}
constexpr void swap(fixed_array& other) noexcept(std::is_nothrow_swappable_v<value_type>) {
std::swap_ranges(begin(), end(), other.begin());
}
template <size_t I>
constexpr value_type& get() & noexcept {
return data[I];
}
template <size_t I>
constexpr value_type&& get() && noexcept {
return std::move(data[I]);
}
template <size_t I>
constexpr const value_type& get() const& noexcept {
return data[I];
}
template <size_t I>
constexpr const value_type&& get() const&& noexcept {
return std::move(data[I]);
}
};
// deduction guides for fixed_array:
template <typename T, size_t N> fixed_array(const T(&)[N]) -> fixed_array<T, N>;
template <typename... Ts> fixed_array(Ts...) -> fixed_array<std::tuple_element_t<0, std::tuple<Ts...>>, sizeof...(Ts)>;
// specializing std::get, std::tuple_size, and std::tuple_element with fixed_array:
namespace std {
template <typename T, size_t N>
struct tuple_size<fixed_array<T, N>> : integral_constant<size_t, N> {};
template <size_t I, typename T, size_t N>
struct tuple_element<I, fixed_array<T, N>> : type_identity<T> {};
using std::get;
template <size_t I, typename T, size_t N>
constexpr T& get(fixed_array<T, N>& arr) noexcept {
return arr[I];
}
template <size_t I, typename T, size_t N>
constexpr T&& get(fixed_array<T, N>&& arr) noexcept {
return std::move(arr[I]);
}
template <size_t I, typename T, size_t N>
constexpr const T& get(const fixed_array<T, N>& arr) noexcept {
return arr[I];
}
template <size_t I, typename T, size_t N>
constexpr const T&& get(const fixed_array<T, N>&& arr) noexcept {
return std::move(arr[I]);
}
namespace ranges {
template <typename T, size_t N>
inline constexpr bool enable_view<fixed_array<T, N>> = true;
}
}
// maybe static polymorphism:
template <size_t N>
struct fixed_string : fixed_array<char, N + 1> {
using fixed_array<char, N + 1>::data;
constexpr fixed_string(const char(&str)[N + 1]) noexcept
requires (std::is_array_v<decltype(str)>) {
std::copy_n(str, N + 1, data);
}
constexpr fixed_string() noexcept {
std::fill_n(data, N + 1, 0);
}
constexpr fixed_string(const char* str) noexcept {
std::copy_n(str, N + 1, data);
}
std::string to_string() const {
return data;
}
// operations
constexpr fixed_string& to_lower() {
std::transform(data, data + N, data, ::tolower);
return *this;
}
constexpr fixed_string& to_upper() {
std::transform(data, data + N, data, ::toupper);
return *this;
}
// stream insert
friend std::ostream& operator<<(std::ostream& os, fixed_string<N> str) {
os << str.data;
return os;
}
};
// deduction guide for fixed_string
template <size_t N> fixed_string(const char(&)[N]) -> fixed_string<N - 1>;
// UDL
#pragma GCC diagnostic ignored "-Wliteral-suffix"
template <fixed_string FS>
constexpr auto operator""fs() {
return fixed_string{FS};
}
#pragma GCC diagnostic warning "-Wliteral-suffix"
// function tests
template <fixed_array Arr>
constexpr auto sum() -> decltype(Arr)::value_type {
return std::accumulate(Arr.begin(), Arr.end(), 0);
}
template <fixed_string S>
void fixed_print() {
for (const auto& i : S) {
std::cout << i << ' ';
} std::cout << '\n';
}
template <fixed_array Arr1, fixed_array Arr2>
constexpr bool is_equal() requires (Arr1.size() == Arr2.size()) {
return Arr1 == Arr2;
}
template <fixed_array Arr1, fixed_array Arr2>
constexpr bool is_not_equal() requires (Arr1.size() == Arr2.size()) {
return Arr1 != Arr2;
}
template <fixed_array Arr1, fixed_array Arr2>
constexpr bool is_less_than() requires (Arr1.size() == Arr2.size()) {
return Arr1 < Arr2;
}
template <fixed_array Arr1, fixed_array Arr2>
constexpr bool is_less_than_or_equal() requires (Arr1.size() == Arr2.size()) {
return Arr1 <= Arr2;
}
template <fixed_array Arr1, fixed_array Arr2>
constexpr bool is_greater_than() requires (Arr1.size() == Arr2.size()) {
return Arr1 > Arr2;
}
template <fixed_array Arr1, fixed_array Arr2>
constexpr bool is_greater_than_or_equal() requires (Arr1.size() == Arr2.size()) {
return Arr1 >= Arr2;
}
Main Function:
int main() {
// test fixed_string:
std::cout << "Chapter 1: Preliminary" << '\n';
fixed_print<"Desmond Gold">();
std::cout << "Sum: " << sum<{1, 2, 3, 4, 5}>() << '\n';
static_assert(sum<{1, 2, 3, 4, 5}>() == 15);
// prelim
int c_arr_1[] = {8, 4, 3};
auto print = [](auto x){ std::cout << x << ' '; };
// testing 1: constructors
[[maybe_unused]] fixed_array arr_1 {1, 2, 3, 4}; // #4
[[maybe_unused]] fixed_array arr_2 = {9, 4, 2, 1, 7}; // #5
[[maybe_unused]] fixed_array arr_3 { arr_1 }; // #4
[[maybe_unused]] fixed_array arr_4 { std::move(arr_2) }; // #5
[[maybe_unused]] fixed_array arr_5 = c_arr_1; // #3
// testing 2: assignments
arr_1 = arr_1;
arr_2 = std::move(arr_2);
arr_3 = {0, 1, 2, 8, 9}; // same length
arr_5 = c_arr_1;
// testing 3: iterators
std::cout << "Chapter 2: Iterators" << '\n';
std::for_each(arr_1.begin(), arr_1.end(), print); std::cout << '\n';
std::for_each(arr_2.cbegin(), arr_2.cend(), print); std::cout << '\n';
// testing 4: element access
std::cout << "Chapter 3: Element Access" << '\n';
for (decltype(arr_5)::size_type i = 0; i < arr_5.size(); i++) {
std::cout << arr_5[i] << ' ';
} std::cout << '\n';
std::cout << "Front: " << arr_1.front() << '\n'
<< "Back: " << arr_1.back() << '\n';
// testing 5: operations
std::cout << "Chapter 4: Fill Operation" << '\n';
arr_5.fill(99);
for (const auto& i : arr_5) {
std::cout << i << ' ';
} std::cout << '\n';
// testing 6: range views
std::cout << "Chapter 5: Range Views" << '\n';
auto reverse_and_filter_array = std::views::reverse | std::views::filter([](auto x){ return x > 1; });
auto transform_array = std::views::transform([](auto x){ return x * x; });
for (const auto& elem : arr_1
| reverse_and_filter_array
| transform_array) {
std::cout << elem << ' ';
} std::cout << '\n';
// testing 7: structured binding
std::cout << "Chapter 6: Structured Binding" << '\n';
[[maybe_unused]] const auto& [x_1, x_2, x_3] = arr_5;
std::cout
<< "x_1: " << x_1 << '\n'
<< "x_2: " << x_2 << '\n'
<< "x_3: " << x_3 << '\n';
std::cout << "Tuple: "
<< std::get<0>(arr_1) << ' '
<< std::get<1>(arr_1) << ' '
<< std::get<2>(arr_1) << ' '
<< std::get<3>(arr_1) << '\n';
// testing 8: equality and lexicographical comparison (must be in the same length)
std::cout << std::boolalpha << "Chapter 7: Comparisons" << '\n';
std::cout
<< "Is equal? [1, 2, 3] and [1, 2, 3]: " << is_equal<{1, 2, 3}, {1, 2, 3}>() << '\n'
<< "Is not equal? [5, 5, 5] and [5, 4, 5]: " << is_not_equal<{5, 5, 5}, {5, 4, 5}>() << '\n'
<< "Is less than? [5, 4, 4] and [4, 4, 4]: " << is_less_than<{5, 4, 4}, {4, 4, 4}>() << '\n'
<< "Is less than or equal? [7, 6, 3] and [7, 7, 7]: " << is_less_than_or_equal<{7, 6, 3}, {7, 7, 7}>() << '\n'
<< "Is greater than? [6, 5, 1] and [1, 5, 6]: " << is_greater_than<{6, 5, 1}, {1, 5, 6}>() << '\n'
<< "Is greater than or equal? [19, 10, 17] and [14, 10, 13]: " << is_greater_than_or_equal<{19, 10, 17}, {14, 10, 13}>() << '\n';
// testing 9: fixed strings
auto string_1 = "First String"fs;
std::cout << "Chapter 8: Fixed Strings" << '\n'
<< string_1 << '\n';
static_assert("Hello"fs == "Hello"fs);
return 0;
}
Output:
Chapter 1: Preliminary
D e s m o n d G o l d
Sum: 15
Chapter 2: Iterators
1 2 3 4
9 4 2 1 7
Chapter 3: Element Access
8 4 3
Front: 1
Back: 4
Chapter 4: Fill Operation
99 99 99
Chapter 5: Range Views
16 9 4
Chapter 6: Structured Binding
x_1: 99
x_2: 99
x_3: 99
Tuple: 1 2 3 4
Chapter 7: Comparisons
Is equal? [1, 2, 3] and [1, 2, 3]: true
Is not equal? [5, 5, 5] and [5, 4, 5]: true
Is less than? [5, 4, 4] and [4, 4, 4]: false
Is less than or equal? [7, 6, 3] and [7, 7, 7]: true
Is greater than? [6, 5, 1] and [1, 5, 6]: true
Is greater than or equal? [19, 10, 17] and [14, 10, 13]: true
Chapter 8: Fixed Strings
First String
So far, there are exactly 0 compilation errors (only in GCC, except for Clang (because it would reject my code using no-underscore UDL, brace-init-list from non-type template parameter, and some ranges-related errors. So it may lead to non-portable code), and no runtime errors with passing static assertions.
Is there anything I could improve?