Mainly this is pretty nice :)
constexpr fixed_array(const std::initializer_list<value_type>& args) noexcept {
std::copy_n(args.begin(), N, data);
}
We definitely need to check that the initializer_list is the correct size, and throw if it isn't.
Note that initializer lists are lightweight (probably just a pair of pointers, or a pointer and a size), so can be passed by value, instead of const&.
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() {}
These guys can all be = default;
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;
}
As can these.
constexpr bool operator==(const fixed_array& other) const {
return std::equal(begin(), end(), other.begin());
}
I believe the spaceship operator will define this for us.
namespace std {
...
}
I think that specializing std::tuple_size and std::tuple_element in the std namespace is ok, but specializing std::get is technically not ok.
For std::get, we should define the get functions in the same namespace as the fixed_array class, so that they work correctly with ADL. i.e. the user can do:
using std::get;
get<0>(a); // will find and use the `get` we defined in the same namespace if fa is our fixed_array type, or std::get if fa is a std type
constexpr fixed_string(const char(&str)[N + 1]) noexcept
requires (std::is_array_v<decltype(str)>) {
std::copy_n(str, N + 1, data);
}
Hmm... I'm guessing that the N + 1 size of fixed_string is so that we can always have a terminating null character. Here we're copying from a char array of N + 1 chars, with no guarantee that the last char is null.
Perhaps we should only accept a size N array? (And manually add the null character).
It might make sense to accept smaller array sizes too? (And fill the rest of the array with nulls).
We probably need to think about other assignment operations here too, and make sure that we have a trailing null.
Consider adding a .c_str() member too.
I guess exactly what you do here depends on the intent. Is the class only for strings of size N exactly, or should it be for any string up to size N?
constexpr fixed_string(const char* str) noexcept {
std::copy_n(str, N + 1, data);
}
Eeep! We have no guarantee that str is long enough. We need to check and throw, or use something like strncpy.
constexpr fixed_string& to_lower() {
std::transform(data, data + N, data, ::tolower);
return *this;
}
Technically, we have to cast our chars to unsigned char when using tolower or toupper.
Also, technically, these functions are in the std:: namespace.
friend std::ostream& operator<<(std::ostream& os, fixed_string<N> str) {
os << str.data;
return os;
}
We could pass the fixed_string by const& instead of by value.