feat: basic_fixed_string implementation and testing improved

This commit is contained in:
Mateusz Pusz
2024-05-16 12:30:45 +02:00
parent 15404cd3a7
commit a479246ea7
10 changed files with 392 additions and 135 deletions

View File

@ -90,6 +90,20 @@
#endif
#if !defined __cpp_lib_ranges_to_container
namespace std {
struct from_range_t {
explicit from_range_t() = default;
};
inline constexpr from_range_t from_range{};
} // namespace std
#endif
#if defined MP_UNITS_COMP_CLANG && MP_UNITS_COMP_CLANG < 17
#define MP_UNITS_CONSTEVAL constexpr

View File

@ -72,45 +72,6 @@ constexpr bool all_of(InputIt first, InputIt last, UnaryPred p)
return find_if_not(first, last, p) == last;
}
template<class InputIt1, class InputIt2>
constexpr bool equal(InputIt1 first1, InputIt1 last1, InputIt2 first2)
{
for (; first1 != last1; ++first1, ++first2) {
if (!(*first1 == *first2)) {
return false;
}
}
return true;
}
template<class I1, class I2, class Cmp>
constexpr auto lexicographical_compare_three_way(I1 first1, I1 last1, I2 first2, I2 last2, Cmp comp)
-> decltype(comp(*first1, *first2))
{
using ret_t = decltype(comp(*first1, *first2));
static_assert(std::disjunction_v<std::is_same<ret_t, std::strong_ordering>, std::is_same<ret_t, std::weak_ordering>,
std::is_same<ret_t, std::partial_ordering>>,
"The return type must be a comparison category type.");
bool exhaust1 = (first1 == last1);
bool exhaust2 = (first2 == last2);
MP_UNITS_DIAGNOSTIC_PUSH
MP_UNITS_DIAGNOSTIC_IGNORE_ZERO_AS_NULLPOINTER_CONSTANT
for (; !exhaust1 && !exhaust2; exhaust1 = (++first1 == last1), exhaust2 = (++first2 == last2))
if (auto c = comp(*first1, *first2); c != 0) return c;
MP_UNITS_DIAGNOSTIC_POP
if (!exhaust1) return std::strong_ordering::greater;
if (!exhaust2) return std::strong_ordering::less;
return std::strong_ordering::equal;
}
template<class I1, class I2>
constexpr auto lexicographical_compare_three_way(I1 first1, I1 last1, I2 first2, I2 last2)
{
return ::mp_units::detail::lexicographical_compare_three_way(first1, last1, first2, last2, std::compare_three_way());
}
template<class ForwardIt>
constexpr ForwardIt max_element(ForwardIt first, ForwardIt last)
{

View File

@ -26,10 +26,10 @@
// NOLINTBEGIN(*-avoid-c-arrays)
#pragma once
// TODO use <algorithm> when moved to C++20 modules (parsing takes too long for each translation unit)
#include <mp-units/bits/hacks.h> // IWYU pragma: keep
#include <mp-units/bits/module_macros.h>
#include <mp-units/compat_macros.h> // IWYU pragma: keep
#include <mp-units/ext/algorithm.h>
#include <mp-units/ext/type_traits.h>
#ifndef MP_UNITS_IN_MODULE_INTERFACE
#include <gsl/gsl-lite.hpp>
@ -49,22 +49,34 @@ namespace mp_units {
* @tparam CharT Character type to be used by the string
* @tparam N The size of the string
*/
template<typename CharT, std::size_t N>
struct basic_fixed_string {
CharT data_[N + 1] = {};
template<typename CharT, std::size_t N, typename Traits = std::char_traits<CharT>>
class basic_fixed_string {
public:
CharT data_[N + 1] = {}; // exposition only
// types
using traits_type = Traits;
using value_type = CharT;
using pointer = CharT*;
using const_pointer = const CharT*;
using reference = CharT&;
using const_reference = const CharT&;
using const_iterator = const CharT*;
using pointer = value_type*;
using const_pointer = const value_type*;
using reference = value_type&;
using const_reference = const value_type&;
using const_iterator = const value_type*;
using iterator = const_iterator;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
using reverse_iterator = const_reverse_iterator;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
// construction and assignment
template<std::convertible_to<CharT>... Chars>
requires(sizeof...(Chars) == N) && (... && !std::is_pointer_v<Chars>)
constexpr explicit basic_fixed_string(Chars... chars) noexcept : data_{chars..., CharT{}}
{
}
// NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions)
constexpr explicit(false) basic_fixed_string(const CharT (&txt)[N + 1]) noexcept
consteval explicit(false) basic_fixed_string(const CharT (&txt)[N + 1])
{
gsl_Expects(txt[N] == CharT{});
for (std::size_t i = 0; i < N; ++i) data_[i] = txt[i];
@ -72,98 +84,195 @@ struct basic_fixed_string {
template<std::input_iterator It, std::sentinel_for<It> S>
requires std::convertible_to<std::iter_value_t<It>, CharT>
constexpr explicit basic_fixed_string(It first, S last) noexcept
constexpr explicit basic_fixed_string(It begin, S end)
{
gsl_Expects(std::distance(first, last) == N);
for (auto it = data_; first != last; ++first, ++it) *it = *first;
gsl_Expects(std::distance(begin, end) == N);
for (auto it = data_; begin != end; ++begin, ++it) *it = *begin;
}
template<std::convertible_to<CharT>... Rest>
requires(1 + sizeof...(Rest) == N)
constexpr explicit basic_fixed_string(CharT first, Rest... rest) noexcept : data_{first, rest..., CharT{}}
template<std::ranges::input_range R>
requires std::convertible_to<std::ranges::range_reference_t<R>, CharT>
constexpr explicit basic_fixed_string(std::from_range_t, R&& r)
{
gsl_Expects(std::ranges::size(r) == N);
for (auto it = data_; auto&& v : std::forward<R>(r)) *it++ = std::forward<decltype(v)>(v);
}
[[nodiscard]] constexpr bool empty() const noexcept { return N == 0; }
[[nodiscard]] constexpr size_type size() const noexcept { return N; }
[[nodiscard]] constexpr const_pointer data() const noexcept { return static_cast<const_pointer>(data_); }
[[nodiscard]] constexpr const CharT* c_str() const noexcept { return data(); }
[[nodiscard]] constexpr value_type operator[](size_type index) const noexcept
{
gsl_Expects(index < N);
return data()[index];
}
constexpr basic_fixed_string(const basic_fixed_string&) noexcept = default;
constexpr basic_fixed_string& operator=(const basic_fixed_string&) noexcept = default;
// iterator support
[[nodiscard]] constexpr const_iterator begin() const noexcept { return data(); }
[[nodiscard]] constexpr const_iterator cbegin() const noexcept { return data(); }
[[nodiscard]] constexpr const_iterator end() const noexcept { return data() + size(); }
[[nodiscard]] constexpr const_iterator cend() const noexcept { return data() + size(); }
[[nodiscard]] constexpr const_iterator cbegin() const noexcept { return begin(); }
[[nodiscard]] constexpr const_iterator cend() const noexcept { return end(); }
[[nodiscard]] constexpr const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); }
[[nodiscard]] constexpr const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); }
[[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); }
[[nodiscard]] constexpr const_reverse_iterator crend() const noexcept { return rend(); }
// NOLINTNEXTLINE(*-explicit-conversions, google-explicit-constructor)
[[nodiscard]] constexpr explicit(false) operator std::basic_string_view<CharT>() const noexcept
// capacity
[[nodiscard]] constexpr size_type size() const noexcept { return N; }
[[nodiscard]] constexpr size_type length() const noexcept { return size(); }
[[nodiscard]] constexpr size_type max_size() const noexcept { return size(); }
[[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; }
// element access
[[nodiscard]] constexpr const_reference operator[](size_type pos) const
{
return std::basic_string_view<CharT>(cbegin(), cend());
gsl_Expects(pos < N);
return data()[pos];
}
[[nodiscard]] constexpr const_reference at(size_type pos) const
{
if (pos >= size()) throw std::out_of_range("basic_fixed_string::at");
return (*this)[pos];
}
[[nodiscard]] constexpr const_reference front() const
{
gsl_Expects(!empty());
return (*this)[0];
}
[[nodiscard]] constexpr const_reference back() const
{
gsl_Expects(!empty());
return (*this)[N - 1];
}
[[nodiscard]] constexpr const_pointer data() const noexcept { return static_cast<const_pointer>(data_); }
// modifiers
constexpr void swap(basic_fixed_string& s) noexcept { std::swap_ranges(begin(), end(), s.begin()); }
// string operations
[[nodiscard]] constexpr const_pointer c_str() const noexcept { return data(); }
[[nodiscard]] constexpr std::basic_string_view<CharT, Traits> view() const noexcept
{
return std::basic_string_view<CharT, Traits>(cbegin(), cend());
}
// NOLINTNEXTLINE(*-explicit-conversions, google-explicit-constructor)
[[nodiscard]] constexpr explicit(false) operator std::basic_string_view<CharT, Traits>() const noexcept
{
return view();
}
template<std::size_t N2>
[[nodiscard]] constexpr friend basic_fixed_string<CharT, N + N2> operator+(
const basic_fixed_string& lhs, const basic_fixed_string<CharT, N2>& rhs) noexcept
[[nodiscard]] constexpr friend basic_fixed_string<CharT, N + N2, Traits> operator+(
const basic_fixed_string& lhs, const basic_fixed_string<CharT, N2, Traits>& rhs) noexcept
{
CharT txt[N + N2 + 1] = {};
CharT txt[N + N2] = {};
for (size_t i = 0; i != N; ++i) txt[i] = lhs[i];
for (size_t i = 0; i != N2; ++i) txt[N + i] = rhs[i];
return basic_fixed_string<CharT, N + N2>(txt);
return basic_fixed_string<CharT, N + N2, Traits>(std::begin(txt), std::end(txt));
}
[[nodiscard]] constexpr bool operator==(const basic_fixed_string&) const = default;
template<std::size_t N2>
[[nodiscard]] friend constexpr bool operator==(const basic_fixed_string&, const basic_fixed_string<CharT, N2>&)
[[nodiscard]] constexpr friend basic_fixed_string<CharT, N + 1, Traits> operator+(const basic_fixed_string& lhs,
CharT rhs) noexcept
{
return false;
CharT txt[N + 1] = {};
for (size_t i = 0; i != N; ++i) txt[i] = lhs[i];
txt[N] = rhs;
return basic_fixed_string<CharT, N + 1, Traits>(std::begin(txt), std::end(txt));
}
[[nodiscard]] constexpr friend basic_fixed_string<CharT, 1 + N, Traits> operator+(
const CharT lhs, const basic_fixed_string& rhs) noexcept
{
CharT txt[1 + N] = {lhs};
for (size_t i = 0; i != N; ++i) txt[1 + i] = rhs[i];
return basic_fixed_string<CharT, 1 + N, Traits>(std::begin(txt), std::end(txt));
}
template<std::size_t N2>
[[nodiscard]] consteval friend basic_fixed_string<CharT, N + N2 - 1, Traits> operator+(
const basic_fixed_string& lhs, const CharT (&rhs)[N2]) noexcept
{
CharT txt[N + N2 - 1] = {};
for (size_t i = 0; i != N; ++i) txt[i] = lhs[i];
for (size_t i = 0; i != N2 - 1; ++i) txt[N + i] = rhs[i];
return basic_fixed_string<CharT, N + N2 - 1, Traits>(std::begin(txt), std::end(txt));
}
template<std::size_t N1>
[[nodiscard]] consteval friend basic_fixed_string<CharT, N1 + N - 1, Traits> operator+(
const CharT (&lhs)[N1], const basic_fixed_string& rhs) noexcept
{
CharT txt[N1 + N - 1] = {};
for (size_t i = 0; i != N1 - 1; ++i) txt[i] = lhs[i];
for (size_t i = 0; i != N; ++i) txt[N1 - 1 + i] = rhs[i];
return basic_fixed_string<CharT, N1 + N - 1, Traits>(std::begin(txt), std::end(txt));
}
// non-member comparison functions
template<size_t N2>
[[nodiscard]] friend constexpr bool operator==(const basic_fixed_string& lhs,
const basic_fixed_string<CharT, N2, Traits>& rhs)
{
return lhs.view() == rhs.view();
}
template<size_t N2>
[[nodiscard]] friend consteval bool operator==(const basic_fixed_string& lhs, const CharT (&rhs)[N2])
{
return lhs.view() == std::basic_string_view<CharT, Traits>(std::cbegin(rhs), std::cend(rhs) - 1);
}
template<size_t N2>
[[nodiscard]] friend constexpr auto operator<=>(const basic_fixed_string& lhs,
const basic_fixed_string<CharT, N2>& rhs)
const basic_fixed_string<CharT, N2, Traits>& rhs)
{
// TODO std::lexicographical_compare_three_way(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
return detail::lexicographical_compare_three_way(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
return lhs.view() <=> rhs.view();
}
template<size_t N2>
[[nodiscard]] friend consteval auto operator<=>(const basic_fixed_string& lhs, const CharT (&rhs)[N2])
{
return lhs.view() <=> std::basic_string_view<CharT, Traits>(std::cbegin(rhs), std::cend(rhs) - 1);
}
template<typename Traits>
// inserters and extractors
friend std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const basic_fixed_string<CharT, N>& str)
const basic_fixed_string& str)
{
return os << str.c_str();
}
};
// deduction guides
template<one_of<char, char8_t, char16_t, char32_t, wchar_t> CharT, std::convertible_to<CharT>... Rest>
basic_fixed_string(CharT, Rest...) -> basic_fixed_string<CharT, 1 + sizeof...(Rest)>;
template<typename CharT, std::size_t N>
basic_fixed_string(const CharT (&str)[N]) -> basic_fixed_string<CharT, N - 1>;
template<typename CharT, std::convertible_to<CharT>... Rest>
basic_fixed_string(CharT, Rest...) -> basic_fixed_string<CharT, 1 + sizeof...(Rest)>;
template<one_of<char, char8_t, char16_t, char32_t, wchar_t> CharT, std::size_t N>
basic_fixed_string(std::from_range_t, std::array<CharT, N>) -> basic_fixed_string<CharT, N>;
// typedef-names
template<std::size_t N>
using fixed_string = basic_fixed_string<char, N>;
template<std::size_t N>
using fixed_u8string = basic_fixed_string<char8_t, N>;
template<std::size_t N>
using fixed_u16string = basic_fixed_string<char16_t, N>;
template<std::size_t N>
using fixed_u32string = basic_fixed_string<char32_t, N>;
template<std::size_t N>
using fixed_wstring = basic_fixed_string<wchar_t, N>;
template<std::size_t N>
using fixed_u8string = basic_fixed_string<char8_t, N>;
template<std::size_t N>
using fixed_u16string = basic_fixed_string<char16_t, N>;
template<std::size_t N>
using fixed_u32string = basic_fixed_string<char32_t, N>;
} // namespace mp_units
// hash support
template<std::size_t N>
struct std::hash<mp_units::fixed_string<N>> : std::hash<std::string_view> {};
template<std::size_t N>
struct std::hash<mp_units::fixed_u8string<N>> : std::hash<std::u8string_view> {};
template<std::size_t N>
struct std::hash<mp_units::fixed_u16string<N>> : std::hash<std::u16string_view> {};
template<std::size_t N>
struct std::hash<mp_units::fixed_u32string<N>> : std::hash<std::u32string_view> {};
template<std::size_t N>
struct std::hash<mp_units::fixed_wstring<N>> : std::hash<std::wstring_view> {};
// formatting support
template<typename CharT, std::size_t N>
struct MP_UNITS_STD_FMT::formatter<mp_units::basic_fixed_string<CharT, N>> : formatter<std::basic_string_view<CharT>> {
template<typename FormatContext>

View File

@ -328,8 +328,7 @@ MP_UNITS_EXPORT template<dimension_symbol_formatting fmt = dimension_symbol_form
return std::string_view(buffer.data(), size);
#else
constexpr std::size_t size = get_size();
constexpr auto buffer = detail::get_symbol_buffer<CharT, size, fmt>(D{});
return basic_fixed_string<CharT, size>(buffer.begin(), buffer.end());
return basic_fixed_string(std::from_range, detail::get_symbol_buffer<CharT, size, fmt>(D{}));
#endif
}

View File

@ -24,8 +24,10 @@
#pragma once
// IWYU pragma: private, include <mp-units/framework.h>
// TODO use <algorithm> when moved to C++20 modules (parsing takes too long for each translation unit)
#include <mp-units/bits/hacks.h>
#include <mp-units/bits/module_macros.h>
#include <mp-units/ext/algorithm.h>
#include <mp-units/ext/fixed_string.h>
#ifndef MP_UNITS_IN_MODULE_INTERFACE
@ -85,7 +87,8 @@ constexpr fixed_u8string<N> to_u8string(fixed_string<N> txt)
* @tparam M The size of the ASCII-only symbol
*/
MP_UNITS_EXPORT template<std::size_t N, std::size_t M>
struct symbol_text {
class symbol_text {
public:
fixed_u8string<N> unicode_;
fixed_string<M> ascii_;
@ -96,7 +99,7 @@ struct symbol_text {
}
// NOLINTNEXTLINE(*-avoid-c-arrays, google-explicit-constructor, hicpp-explicit-conversions)
constexpr explicit(false) symbol_text(const char (&txt)[N + 1]) :
consteval explicit(false) symbol_text(const char (&txt)[N + 1]) :
unicode_(detail::to_u8string(basic_fixed_string{txt})), ascii_(txt)
{
gsl_Expects(txt[N] == char{});
@ -110,7 +113,7 @@ struct symbol_text {
}
// NOLINTNEXTLINE(*-avoid-c-arrays)
constexpr symbol_text(const char8_t (&u)[N + 1], const char (&a)[M + 1]) : unicode_(u), ascii_(a)
consteval symbol_text(const char8_t (&u)[N + 1], const char (&a)[M + 1]) : unicode_(u), ascii_(a)
{
gsl_Expects(u[N] == char8_t{});
gsl_Expects(a[M] == char{});

View File

@ -854,8 +854,7 @@ MP_UNITS_EXPORT template<unit_symbol_formatting fmt = unit_symbol_formatting{},
return std::string_view(buffer.data(), size);
#else
constexpr std::size_t size = get_size();
constexpr auto buffer = detail::get_symbol_buffer<CharT, size, fmt>(U{});
return basic_fixed_string<CharT, size>(buffer.begin(), buffer.end());
return basic_fixed_string(std::from_range, detail::get_symbol_buffer<CharT, size, fmt>(U{}));
#endif
}

View File

@ -24,7 +24,9 @@ cmake_minimum_required(VERSION 3.5)
find_package(Catch2 3 REQUIRED)
add_executable(unit_tests_runtime distribution_test.cpp fmt_test.cpp math_test.cpp atomic_test.cpp)
add_executable(
unit_tests_runtime distribution_test.cpp fixed_string_test.cpp fmt_test.cpp math_test.cpp atomic_test.cpp
)
if(${projectPrefix}BUILD_CXX_MODULES)
target_compile_definitions(unit_tests_runtime PUBLIC ${projectPrefix}MODULES)
endif()

View File

@ -0,0 +1,67 @@
// The MIT License (MIT)
//
// Copyright (c) 2018 Mateusz Pusz
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_exception.hpp>
#include <mp-units/compat_macros.h>
#include <string_view>
#ifdef MP_UNITS_MODULES
import mp_units;
#else
#include <mp-units/ext/fixed_string.h>
#endif
using namespace mp_units;
TEST_CASE("fixed_string::at", "[fixed_string]")
{
basic_fixed_string txt = "abc";
SECTION("in range")
{
CHECK(txt.at(0) == 'a');
CHECK(txt.at(1) == 'b');
CHECK(txt.at(2) == 'c');
}
SECTION("out of range")
{
REQUIRE_THROWS_MATCHES(txt.at(3), std::out_of_range, Catch::Matchers::Message("basic_fixed_string::at"));
REQUIRE_THROWS_MATCHES(txt.at(1024), std::out_of_range, Catch::Matchers::Message("basic_fixed_string::at"));
}
}
TEST_CASE("fixed_string text output", "[fixed_string][ostream][fmt]")
{
basic_fixed_string txt = "units";
SECTION("iostream")
{
std::ostringstream os;
os << txt;
CHECK(os.str() == "units");
}
SECTION("fmt") { CHECK(MP_UNITS_STD_FMT::format("{}", txt) == "units"); }
}
TEST_CASE("fixed_string hash", "[fixed_string][hash]")
{
basic_fixed_string txt = "units";
CHECK(std::hash<fixed_string<5>>{}(txt) == std::hash<std::string_view>{}("units"));
}

View File

@ -34,7 +34,6 @@
#ifdef MP_UNITS_MODULES
import mp_units;
#else
#include <mp-units/ext/fixed_string.h>
#include <mp-units/format.h>
#include <mp-units/ostream.h> // IWYU pragma: keep
#include <mp-units/systems/cgs.h>
@ -51,18 +50,6 @@ inline constexpr bool mp_units::is_vector<T> = true;
using namespace mp_units;
using namespace mp_units::si::unit_symbols;
TEST_CASE("fixed_string", "[text][ostream][fmt]")
{
basic_fixed_string txt = "units";
SECTION("iostream")
{
std::ostringstream os;
os << txt;
CHECK(os.str() == "units");
}
SECTION("fmt") { CHECK(MP_UNITS_STD_FMT::format("{}", txt) == "units"); }
}
TEST_CASE("operator<< on a quantity", "[text][ostream][fmt]")
{
std::ostringstream os;

View File

@ -27,36 +27,152 @@ using namespace mp_units;
namespace {
constexpr std::array array = {'a', 'b', 'c'};
auto from_string = [] {
std::string txt = "abc";
return fixed_string<3>(std::from_range, txt);
};
auto from_string_iter = [] {
std::string txt = "abc";
return fixed_string<3>(txt.begin(), txt.end());
};
constexpr fixed_string<0> txt0;
constexpr basic_fixed_string txt1('a');
constexpr basic_fixed_string txt2('a', 'b', 'c');
constexpr basic_fixed_string txt3 = "abc";
constexpr fixed_string<3> txt4(array.begin(), array.end());
constexpr basic_fixed_string txt5(std::from_range, array);
constexpr basic_fixed_string txt6(from_string());
constexpr basic_fixed_string txt7(from_string_iter());
constexpr fixed_string<3> txt8(txt2.begin(), txt2.end());
constexpr fixed_string<3> txt9(txt2.rbegin(), txt2.rend());
static_assert(txt0.size() == 0);
static_assert(txt1.size() == 1);
static_assert(txt2.size() == 3);
static_assert(txt3.size() == 3);
static_assert(txt4.size() == 3);
static_assert(txt5.size() == 3);
static_assert(txt6.size() == 3);
static_assert(txt7.size() == 3);
static_assert(txt8.size() == 3);
static_assert(txt9.size() == 3);
static_assert(txt0.length() == 0);
static_assert(txt1.length() == 1);
static_assert(txt2.length() == 3);
static_assert(txt0.max_size() == 0);
static_assert(txt1.max_size() == 1);
static_assert(txt2.max_size() == 3);
static_assert(txt0.empty() == true);
static_assert(txt1.empty() == false);
static_assert(txt2.empty() == false);
static_assert(txt3.empty() == false);
static_assert(txt4.empty() == false);
static_assert(txt5.empty() == false);
static_assert(txt6.empty() == false);
static_assert(txt7.empty() == false);
static_assert(txt8.empty() == false);
static_assert(txt9.empty() == false);
static_assert(txt1[0] == 'a');
static_assert(txt2[0] == 'a');
static_assert(txt2[1] == 'b');
static_assert(txt2[2] == 'c');
static_assert(txt9[0] == 'c');
static_assert(txt9[1] == 'b');
static_assert(txt9[2] == 'a');
static_assert(txt1.at(0) == 'a');
static_assert(txt2.at(0) == 'a');
static_assert(txt2.at(1) == 'b');
static_assert(txt2.at(2) == 'c');
static_assert(txt9.at(0) == 'c');
static_assert(txt9.at(1) == 'b');
static_assert(txt9.at(2) == 'a');
static_assert(txt1.front() == 'a');
static_assert(txt1.back() == 'a');
static_assert(txt2.front() == 'a');
static_assert(txt2.back() == 'c');
static_assert(txt5.front() == 'a');
static_assert(txt5.back() == 'c');
static_assert(txt6.front() == 'a');
static_assert(txt6.back() == 'c');
static_assert(txt7.front() == 'a');
static_assert(txt7.back() == 'c');
static_assert(txt8.front() == 'a');
static_assert(txt8.back() == 'c');
static_assert(txt9.front() == 'c');
static_assert(txt9.back() == 'a');
static_assert(std::string_view(txt0.data()) == "");
static_assert(std::string_view(txt0.c_str()) == "");
static_assert(std::string_view(txt1.data()) == "a");
static_assert(std::string_view(txt1.c_str()) == "a");
static_assert(std::string_view(txt2.data()) == "abc");
static_assert(std::string_view(txt2.c_str()) == "abc");
static_assert(txt0 == "");
static_assert("a" == txt1);
static_assert(txt2 == "abc");
static_assert(txt3 == "abc");
static_assert(txt4 == "abc");
static_assert(txt5 == "abc");
static_assert(txt6 == "abc");
static_assert(txt7 == "abc");
static_assert(txt8 == "abc");
static_assert(txt9 == "cba");
static_assert(txt1 == basic_fixed_string("a"));
static_assert(txt1 != basic_fixed_string("b"));
static_assert(txt1 != basic_fixed_string("aa"));
static_assert(txt1 < basic_fixed_string("b"));
static_assert(txt1 < basic_fixed_string("aa"));
static_assert(txt1 + basic_fixed_string('b') == basic_fixed_string("ab"));
static_assert(basic_fixed_string('b') + txt1 == basic_fixed_string("ba"));
static_assert(txt1 + basic_fixed_string("bc") == basic_fixed_string("abc"));
static_assert(basic_fixed_string("bc") + txt1 == basic_fixed_string("bca"));
static_assert(txt1 == "a");
static_assert(txt1 != "b");
static_assert(txt1 != "aa");
static_assert(txt1 < "b");
static_assert(txt1 < "aa");
static_assert(txt1 + basic_fixed_string('b') == "ab");
static_assert(basic_fixed_string('b') + txt1 == "ba");
static_assert(txt1 + basic_fixed_string("bc") == "abc");
static_assert(basic_fixed_string("bc") + txt1 == "bca");
static_assert(txt1 + 'b' == "ab");
static_assert('b' + txt1 == "ba");
static_assert(txt1 + "bc" == "abc");
static_assert("bc" + txt1 == "bca");
constexpr basic_fixed_string txt2("abc");
static_assert(txt2.size() == 3);
static_assert(txt2[0] == 'a');
static_assert(txt2[1] == 'b');
static_assert(txt2[2] == 'c');
static_assert(txt2 == basic_fixed_string("abc"));
static_assert(txt2 != basic_fixed_string("cba"));
static_assert(txt2 != basic_fixed_string("abcd"));
static_assert(txt2 < basic_fixed_string("b"));
static_assert(txt2 > basic_fixed_string("aa"));
static_assert(txt2 + basic_fixed_string('d') == basic_fixed_string("abcd"));
static_assert(basic_fixed_string('d') + txt2 == basic_fixed_string("dabc"));
static_assert(txt2 + basic_fixed_string("def") == basic_fixed_string("abcdef"));
static_assert(basic_fixed_string("def") + txt2 == basic_fixed_string("defabc"));
static_assert(txt2 == "abc");
static_assert(txt2 != "cba");
static_assert(txt2 != "abcd");
static_assert(txt2 < "b");
static_assert(txt2 > "aa");
#ifndef MP_UNITS_COMP_GCC
static_assert(std::string_view(basic_fixed_string("abcd")).find('c') == 2);
#endif
static_assert(txt2 + basic_fixed_string('d') == "abcd");
static_assert(basic_fixed_string('d') + txt2 == "dabc");
static_assert(txt2 + basic_fixed_string("def") == "abcdef");
static_assert(basic_fixed_string("def") + txt2 == "defabc");
static_assert(txt2 + 'd' == "abcd");
static_assert('d' + txt2 == "dabc");
static_assert(txt2 + "def" == "abcdef");
static_assert("def" + txt2 == "defabc");
static_assert(std::string_view(txt2) == "abc");
static_assert(txt2.view() == "abc");
static_assert(std::string_view(txt2).find('b') == 1);
static_assert(txt2.view().find('b') == 1);
} // namespace