From cf7ad336238d36993008244065fd51f3ad4eb087 Mon Sep 17 00:00:00 2001 From: Gennaro Prota Date: Fri, 19 Dec 2025 12:32:50 +0100 Subject: [PATCH] Add an experimental basic_static_cstring template in example/ This introduces an alternative to basic_static_string designed for use in POD types: Trivially copyable, having a sizeof == N + 1, with no embedded NULs. Placed in example/ to gather user feedback before committing to a public API. See issue #23. --- .github/workflows/example.yml | 156 ++++ example/static_cstring/Jamfile | 19 + example/static_cstring/README.md | 13 + example/static_cstring/static_cstring.hpp | 441 ++++++++++++ .../static_cstring/static_cstring_test.cpp | 671 ++++++++++++++++++ 5 files changed, 1300 insertions(+) create mode 100644 .github/workflows/example.yml create mode 100644 example/static_cstring/Jamfile create mode 100644 example/static_cstring/README.md create mode 100644 example/static_cstring/static_cstring.hpp create mode 100644 example/static_cstring/static_cstring_test.cpp diff --git a/.github/workflows/example.yml b/.github/workflows/example.yml new file mode 100644 index 0000000..75c5c68 --- /dev/null +++ b/.github/workflows/example.yml @@ -0,0 +1,156 @@ +# +# Copyright (c) 2025 Gennaro Prota (gennaro dot prota at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +# +# Official repository: https://github.com/boostorg/static_string +# + +name: Example (basic_static_cstring) + +on: + pull_request: + push: + branches: + - master + - develop + - bugfix/** + - feature/** + - fix/** + - github/** + - pr/** + paths-ignore: + - LICENSE + - meta/** + - README.md + +jobs: + linux: + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + include: + - { toolset: gcc-13, cxxstd: '20,23' } + - { toolset: gcc-14, cxxstd: '20,23,26' } + - { toolset: clang-17, cxxstd: '20,23' } + - { toolset: clang-18, cxxstd: '20,23,26' } + + steps: + - name: Checkout Boost super-project + uses: actions/checkout@v4 + with: + repository: boostorg/boost + ref: develop + fetch-depth: 0 + + - name: Checkout this library + uses: actions/checkout@v4 + with: + path: libs/static_string + fetch-depth: 0 + + - name: Initialize Boost submodules + run: | + git submodule update --init tools/boostdep + python tools/boostdep/depinst/depinst.py --git_args '--jobs 4' static_string + + - name: Bootstrap b2 + run: ./bootstrap.sh + + - name: Generate Boost headers + run: ./b2 headers + + - name: Build and run example tests + run: | + ./b2 libs/static_string/example/static_cstring \ + toolset=${{ matrix.toolset }} \ + cxxstd=${{ matrix.cxxstd }} \ + variant=debug,release \ + -j$(nproc) + + macos: + runs-on: macos-14 + strategy: + fail-fast: false + matrix: + include: + - { toolset: clang, cxxstd: '20,23' } + + steps: + - name: Checkout Boost super-project + uses: actions/checkout@v4 + with: + repository: boostorg/boost + ref: develop + fetch-depth: 0 + + - name: Checkout this library + uses: actions/checkout@v4 + with: + path: libs/static_string + fetch-depth: 0 + + - name: Initialize Boost submodules + run: | + git submodule update --init tools/boostdep + python3 tools/boostdep/depinst/depinst.py --git_args '--jobs 4' static_string + + - name: Bootstrap b2 + run: ./bootstrap.sh + + - name: Generate Boost headers + run: ./b2 headers + + - name: Build and run example tests + run: | + ./b2 libs/static_string/example/static_cstring \ + toolset=${{ matrix.toolset }} \ + cxxstd=${{ matrix.cxxstd }} \ + variant=debug,release \ + -j$(sysctl -n hw.ncpu) + + windows: + runs-on: windows-2022 + strategy: + fail-fast: false + matrix: + include: + - { toolset: msvc-14.3, cxxstd: '20,latest' } + + steps: + - name: Checkout Boost super-project + uses: actions/checkout@v4 + with: + repository: boostorg/boost + ref: develop + fetch-depth: 0 + + - name: Checkout this library + uses: actions/checkout@v4 + with: + path: libs/static_string + fetch-depth: 0 + + - name: Initialize Boost submodules + run: | + git submodule update --init tools/boostdep + python tools/boostdep/depinst/depinst.py --git_args '--jobs 4' static_string + + - name: Bootstrap b2 + run: .\bootstrap.bat + shell: cmd + + - name: Generate Boost headers + run: .\b2 headers + shell: cmd + + - name: Build and run example tests + run: | + .\b2 libs/static_string/example/static_cstring ^ + toolset=${{ matrix.toolset }} ^ + cxxstd=${{ matrix.cxxstd }} ^ + variant=debug,release ^ + address-model=64 + shell: cmd diff --git a/example/static_cstring/Jamfile b/example/static_cstring/Jamfile new file mode 100644 index 0000000..c858d59 --- /dev/null +++ b/example/static_cstring/Jamfile @@ -0,0 +1,19 @@ +# +# Copyright (c) 2025 Gennaro Prota (gennaro dot prota at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +# +# Official repository: https://github.com/boostorg/static_string +# + +import testing ; + +project + : requirements + ../../include + extra + 20 + ; + +run static_cstring_test.cpp ; diff --git a/example/static_cstring/README.md b/example/static_cstring/README.md new file mode 100644 index 0000000..ebf965e --- /dev/null +++ b/example/static_cstring/README.md @@ -0,0 +1,13 @@ +This directory contains an experimental implementation of `basic_static_cstring`, which differs from `basic_static_string` in the following ways: + +| | `basic_static_cstring` | `basic_static_string` | +|---------------------|------------------------|-----------------------| +| Layout | `sizeof == N + 1` | Has size member | +| Embedded NULs | Not supported | Supported | +| Trivially copyable | Yes | No | + +Additionally, when `N <= UCHAR_MAX`, `basic_static_cstring` employs an optimization that avoids calling `std::strlen()` to compute the size. + +This work stems from [boostorg/static_string#23](https://github.com/boostorg/static_string/issues/23). + +If you believe `basic_static_cstring` should become part of the public API, please share your feedback on the Boost mailing list. \ No newline at end of file diff --git a/example/static_cstring/static_cstring.hpp b/example/static_cstring/static_cstring.hpp new file mode 100644 index 0000000..1552fd8 --- /dev/null +++ b/example/static_cstring/static_cstring.hpp @@ -0,0 +1,441 @@ +// +// Copyright (c) 2025 Gennaro Prota (gennaro dot prota at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/static_string +// + +#ifndef BOOST_STATIC_STRING_STATIC_CSTRING_HPP +#define BOOST_STATIC_STRING_STATIC_CSTRING_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace static_strings { + +namespace detail { + +// Primary template: No remaining-capacity trick; uses traits::length() for length. +template +class static_cstring_base +{ +public: + using traits_type = Traits; + using value_type = CharT; + using size_type = std::size_t; + + value_type data_[N + 1]{}; + + constexpr size_type get_size() const noexcept + { + return traits_type::length(data_); + } + + constexpr void set_size(size_type sz) noexcept + { + data_[sz] = value_type{}; + } + + // Defaulted comparisons for structural type support. + constexpr bool operator==(const static_cstring_base&) const noexcept = default; + constexpr auto operator<=>(const static_cstring_base&) const noexcept = default; +}; + +// Specialization for N <= UCHAR_MAX: Uses remaining-capacity trick. +template +class static_cstring_base +{ +public: + using traits_type = Traits; + using value_type = CharT; + using size_type = std::size_t; + + value_type data_[N + 1]{}; + + constexpr size_type get_size() const noexcept + { + return N - static_cast(data_[N]); + } + + constexpr void set_size(size_type sz) noexcept + { + data_[sz] = value_type{}; + data_[N] = static_cast(N - sz); + } + + // Defaulted comparisons for structural type support. + constexpr bool operator==(const static_cstring_base&) const noexcept = default; + constexpr auto operator<=>(const static_cstring_base&) const noexcept = default; +}; + +} // namespace detail + +template> +class basic_static_cstring + : public detail::static_cstring_base +{ +public: + using base = detail::static_cstring_base; + using base::data_; + using base::get_size; + using base::set_size; + + // Member types + using traits_type = Traits; + using value_type = CharT; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using iterator = pointer; + using const_iterator = const_pointer; + + static constexpr size_type npos = static_cast(-1); + static constexpr size_type static_capacity = N; + + // Constructors. + constexpr basic_static_cstring() noexcept + { + set_size(0); + } + + constexpr basic_static_cstring(const CharT* s) + { + assign(s); + } + + constexpr basic_static_cstring(const CharT* s, size_type count) + { + assign(s, count); + } + + constexpr basic_static_cstring(size_type count, CharT ch) + { + assign(count, ch); + } + + template + constexpr basic_static_cstring(const CharT (&arr)[M]) + { + static_assert(M <= N + 1, "String literal too long for static_cstring"); + assign(arr, M - 1); + } + + constexpr size_type size() const noexcept + { + return get_size(); + } + + constexpr size_type length() const noexcept + { + return size(); + } + + constexpr bool empty() const noexcept + { + return data_[0] == value_type{}; + } + + static constexpr size_type max_size() noexcept + { + return N; + } + + static constexpr size_type capacity() noexcept + { + return N; + } + + // Element access. + constexpr reference operator[](size_type pos) noexcept + { + return data_[pos]; + } + + constexpr const_reference operator[](size_type pos) const noexcept + { + return data_[pos]; + } + + constexpr reference at(size_type pos) + { + if (pos >= size()) + { + throw std::out_of_range("static_cstring::at"); + } + return data_[pos]; + } + + constexpr const_reference at(size_type pos) const + { + if (pos >= size()) + { + throw std::out_of_range("static_cstring::at"); + } + return data_[pos]; + } + + constexpr reference front() noexcept + { + BOOST_STATIC_STRING_ASSERT(!empty()); + return data_[0]; + } + + constexpr const_reference front() const noexcept + { + BOOST_STATIC_STRING_ASSERT(!empty()); + return data_[0]; + } + + constexpr reference back() noexcept + { + BOOST_STATIC_STRING_ASSERT(!empty()); + return data_[size() - 1]; + } + + constexpr const_reference back() const noexcept + { + BOOST_STATIC_STRING_ASSERT(!empty()); + return data_[size() - 1]; + } + + constexpr pointer data() noexcept + { + return data_; + } + + constexpr const_pointer data() const noexcept + { + return data_; + } + + constexpr const_pointer c_str() const noexcept + { + return data_; + } + + // Iterators. + constexpr iterator begin() noexcept + { + return data_; + } + + constexpr const_iterator begin() const noexcept + { + return data_; + } + + constexpr const_iterator cbegin() const noexcept + { + return data_; + } + + constexpr iterator end() noexcept + { + return data_ + size(); + } + + constexpr const_iterator end() const noexcept + { + return data_ + size(); + } + + constexpr const_iterator cend() const noexcept + { + return data_ + size(); + } + + // Modifiers. + constexpr void clear() noexcept + { + set_size(0); + } + + constexpr basic_static_cstring& assign(const CharT* s) + { + return assign(s, traits_type::length(s)); + } + + constexpr basic_static_cstring& assign(const CharT* s, size_type count) + { + if (count > N) + { + throw std::length_error("static_cstring::assign"); + } + traits_type::copy(data_, s, count); + set_size(count); + return *this; + } + + constexpr basic_static_cstring& assign(size_type count, CharT ch) + { + if (count > N) + { + throw std::length_error("static_cstring::assign"); + } + traits_type::assign(data_, count, ch); + set_size(count); + return *this; + } + + constexpr basic_static_cstring& operator=(const CharT* s) + { + return assign(s); + } + + constexpr void push_back(CharT ch) + { + const size_type sz = size(); + if (sz >= N) + { + throw std::length_error("static_cstring::push_back"); + } + data_[sz] = ch; + set_size(sz + 1); + } + + constexpr void pop_back() noexcept + { + BOOST_STATIC_STRING_ASSERT(!empty()); + set_size(size() - 1); + } + + constexpr basic_static_cstring& append(const CharT* s) + { + return append(s, traits_type::length(s)); + } + + constexpr basic_static_cstring& append(const CharT* s, size_type count) + { + const size_type sz = size(); + if (sz + count > N) + { + throw std::length_error("static_cstring::append"); + } + traits_type::copy(data_ + sz, s, count); + set_size(sz + count); + return *this; + } + + constexpr basic_static_cstring& append(size_type count, CharT ch) + { + const size_type sz = size(); + if (sz + count > N) + { + throw std::length_error("static_cstring::append"); + } + traits_type::assign(data_ + sz, count, ch); + set_size(sz + count); + return *this; + } + + constexpr basic_static_cstring& operator+=(const CharT* s) + { + return append(s); + } + + constexpr basic_static_cstring& operator+=(CharT ch) + { + push_back(ch); + return *this; + } + + // Comparisons. + constexpr int compare(const basic_static_cstring& other) const noexcept + { + const size_type lhs_sz = size(); + const size_type rhs_sz = other.size(); + const int result = traits_type::compare(data_, other.data_, (std::min)(lhs_sz, rhs_sz)); + + return result != 0 + ? result + : lhs_sz < rhs_sz + ? -1 + : lhs_sz > rhs_sz + ? 1 + : 0; + } + + constexpr int compare(const CharT* s) const noexcept + { + return compare(basic_static_cstring(s)); + } + + // Conversions. + constexpr operator std::basic_string_view() const noexcept + { + return {data_, size()}; + } + + std::basic_string str() const + { + return {data_, size()}; + } + + // Swap. + constexpr void swap(basic_static_cstring& other) noexcept + { + basic_static_cstring tmp = *this; + *this = other; + other = tmp; + } + + // Defaulted comparisons for structural type (C++20). + constexpr bool operator==(const basic_static_cstring&) const noexcept = default; + constexpr auto operator<=>(const basic_static_cstring&) const noexcept = default; +}; + +#if defined(BOOST_STATIC_STRING_USE_DEDUCT) + +// Deduction guide. +template +basic_static_cstring(const CharT(&)[N]) -> basic_static_cstring; + +#endif + +// Comparison with const CharT*. +template +constexpr bool operator==(const basic_static_cstring& lhs, + const CharT* rhs) noexcept +{ + return lhs.compare(rhs) == 0; +} + +template +constexpr bool operator==(const CharT* lhs, + const basic_static_cstring& rhs) noexcept +{ + return rhs.compare(lhs) == 0; +} + +// Stream output. +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const basic_static_cstring& str) +{ + return os << str.c_str(); +} + +// Alias templates. +template +using static_cstring = basic_static_cstring; + +template +using static_wcstring = basic_static_cstring; + +} +} + +#endif diff --git a/example/static_cstring/static_cstring_test.cpp b/example/static_cstring/static_cstring_test.cpp new file mode 100644 index 0000000..40dd4ce --- /dev/null +++ b/example/static_cstring/static_cstring_test.cpp @@ -0,0 +1,671 @@ +// +// Copyright (c) 2025 Gennaro Prota (gennaro dot prota at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/static_string +// + +#include "static_cstring.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace static_strings { + +static +void +testCStringSizeGuarantee() +{ + // Core guarantee: sizeof is exactly N + 1 (no size member). + static_assert(sizeof(static_cstring<0>) == 1, ""); + static_assert(sizeof(static_cstring<1>) == 2, ""); + static_assert(sizeof(static_cstring<10>) == 11, ""); + static_assert(sizeof(static_cstring<63>) == 64, ""); + static_assert(sizeof(static_cstring<64>) == 65, ""); + static_assert(sizeof(static_cstring<127>) == 128, ""); + static_assert(sizeof(static_cstring) == (UCHAR_MAX + 1), ""); + static_assert(sizeof(static_cstring) == (UCHAR_MAX + 2), ""); + static_assert(sizeof(static_cstring<1000>) == 1001, ""); +} + +static +void +testCStringRemainingCapacityTrick() +{ + // Test the remaining-capacity optimization for N <= UCHAR_MAX. + + // Full capacity: Last byte is both null terminator AND remaining == 0. + { + static_cstring<5> full("12345"); + BOOST_TEST(full.size() == 5); + BOOST_TEST(full.capacity() == 5); + BOOST_TEST(full.data()[5] == '\0'); + BOOST_TEST(std::strcmp(full.c_str(), "12345") == 0); + } + + // Partial fill: Null terminator at position size, remaining at position N. + { + static_cstring<10> partial("Hi"); + BOOST_TEST(partial.size() == 2); + BOOST_TEST(partial.capacity() == 10); + BOOST_TEST(partial.data()[2] == '\0'); + BOOST_TEST(static_cast(partial.data()[10]) == 8); + } + + // Empty string. + { + static_cstring<10> empty; + BOOST_TEST(empty.size() == 0); + BOOST_TEST(empty.capacity() == 10); + BOOST_TEST(empty.data()[0] == '\0'); + BOOST_TEST(static_cast(empty.data()[10]) == 10); + } + + // Edge case: N == UCHAR_MAX (max for our trick). + { + static_cstring large; + large.assign(100, 'x'); + BOOST_TEST(large.size() == 100); + BOOST_TEST(large.capacity() == UCHAR_MAX); + BOOST_TEST(static_cast(large.data()[UCHAR_MAX]) == (UCHAR_MAX - large.size())); + } +} + +template +struct CStringTypeTraits +{ + static_assert(std::is_trivially_copyable::value); + static_assert(std::is_trivially_copy_constructible::value); + static_assert(std::is_trivially_move_constructible::value); + static_assert(std::is_trivially_copy_assignable::value); + static_assert(std::is_trivially_move_assignable::value); + static_assert(std::is_trivially_destructible::value); +}; + +static +void +testCStringTypeTraits() +{ + { + using S = static_cstring<0>; + CStringTypeTraits< S > check; + static_cast(check); + } + { + using S = static_cstring<63>; + CStringTypeTraits< S > check; + static_cast(check); + } + { + using S = static_cstring<300>; + CStringTypeTraits< S > check; + static_cast(check); + } +} + +static +void +testCStringConstruct() +{ + // Default construction. + { + static_cstring<1> s; + BOOST_TEST(s.empty()); + BOOST_TEST(s.size() == 0); + BOOST_TEST(s == ""); + BOOST_TEST(*s.end() == 0); + } + + // Construct with count and char. + { + static_cstring<4> s1(3, 'x'); + BOOST_TEST(!s1.empty()); + BOOST_TEST(s1.size() == 3); + BOOST_TEST(s1 == "xxx"); + BOOST_TEST(*s1.end() == 0); + BOOST_TEST_THROWS( + (static_cstring<2>(3, 'x')), + std::length_error); + } + + // Construct from a C string. + { + static_cstring<5> s1("12345"); + BOOST_TEST(s1.size() == 5); + BOOST_TEST(s1 == "12345"); + BOOST_TEST(*s1.end() == 0); + BOOST_TEST_THROWS( + (static_cstring<4>("12345")), + std::length_error); + } + + // Construct from a C string with count. + { + static_cstring<5> s1("UVXYZ", 3); + BOOST_TEST(s1 == "UVX"); + BOOST_TEST(*s1.end() == 0); + } + + // Copy construction. + { + static_cstring<5> s1("12345"); + static_cstring<5> s2(s1); + BOOST_TEST(s2 == "12345"); + BOOST_TEST(*s2.end() == 0); + } +} + +static +void +testCStringAssignment() +{ + // assign(size_type count, CharT ch). + BOOST_TEST(static_cstring<3>{}.assign(1, '*') == "*"); + BOOST_TEST(static_cstring<3>{}.assign(3, '*') == "***"); + BOOST_TEST(static_cstring<3>{"abc"}.assign(3, '*') == "***"); + BOOST_TEST_THROWS(static_cstring<1>{"a"}.assign(2, '*'), std::length_error); + + // assign(CharT const* s, size_type count). + BOOST_TEST(static_cstring<3>{}.assign("abc", 3) == "abc"); + BOOST_TEST(static_cstring<3>{"*"}.assign("abc", 3) == "abc"); + BOOST_TEST_THROWS(static_cstring<1>{}.assign("abc", 3), std::length_error); + + // assign(CharT const* s). + BOOST_TEST(static_cstring<3>{}.assign("abc") == "abc"); + BOOST_TEST(static_cstring<3>{"*"}.assign("abc") == "abc"); + BOOST_TEST_THROWS(static_cstring<1>{}.assign("abc"), std::length_error); + + // operator=(const CharT* s). + { + static_cstring<3> s1; + s1 = "123"; + BOOST_TEST(s1 == "123"); + BOOST_TEST(*s1.end() == 0); + static_cstring<1> s2; + BOOST_TEST_THROWS( + s2 = "123", + std::length_error); + } + + // Copy assignment. + { + static_cstring<3> s1("123"); + static_cstring<3> s2; + s2 = s1; + BOOST_TEST(s2 == "123"); + BOOST_TEST(*s2.end() == 0); + } +} + +static +void +testCStringElements() +{ + using ccs3 = static_cstring<3> const; + + // at(size_type pos). + BOOST_TEST(static_cstring<3>{"abc"}.at(0) == 'a'); + BOOST_TEST(static_cstring<3>{"abc"}.at(2) == 'c'); + BOOST_TEST_THROWS(static_cstring<3>{""}.at(0), std::out_of_range); + BOOST_TEST_THROWS(static_cstring<3>{"abc"}.at(4), std::out_of_range); + + // at(size_type pos) const. + BOOST_TEST(ccs3{"abc"}.at(0) == 'a'); + BOOST_TEST(ccs3{"abc"}.at(2) == 'c'); + BOOST_TEST_THROWS(ccs3{""}.at(0), std::out_of_range); + + // operator[](size_type pos). + BOOST_TEST(static_cstring<3>{"abc"}[0] == 'a'); + BOOST_TEST(static_cstring<3>{"abc"}[2] == 'c'); + BOOST_TEST(static_cstring<3>{"abc"}[3] == 0); + BOOST_TEST(static_cstring<3>{""}[0] == 0); + + // front() / back(). + BOOST_TEST(static_cstring<3>{"abc"}.front() == 'a'); + BOOST_TEST(static_cstring<3>{"abc"}.back() == 'c'); + + // data() / c_str(). + { + static_cstring<3> s("123"); + BOOST_TEST(std::memcmp(s.data(), "123", 3) == 0); + BOOST_TEST(std::memcmp(s.c_str(), "123\0", 4) == 0); + } + + // Modification through element access. + { + static_cstring<5> s("12345"); + s[1] = '_'; + BOOST_TEST(s == "1_345"); + s.front() = 'A'; + BOOST_TEST(s == "A_345"); + s.back() = 'Z'; + BOOST_TEST(s == "A_34Z"); + } +} + +static +void +testCStringIterators() +{ + { + static_cstring<3> s; + BOOST_TEST(std::distance(s.begin(), s.end()) == 0); + s = "123"; + BOOST_TEST(std::distance(s.begin(), s.end()) == 3); + } + { + static_cstring<3> const s("123"); + BOOST_TEST(std::distance(s.begin(), s.end()) == 3); + BOOST_TEST(std::distance(s.cbegin(), s.cend()) == 3); + } + + // Iteration. + { + static_cstring<5> s("hello"); + std::string result; + for (const char c : s) + { + result += c; + } + BOOST_TEST(result == "hello"); + } +} + +static +void +testCStringCapacity() +{ + // empty(). + BOOST_TEST(static_cstring<0>{}.empty()); + BOOST_TEST(static_cstring<1>{}.empty()); + BOOST_TEST(!static_cstring<1>{"a"}.empty()); + + // size(). + BOOST_TEST(static_cstring<0>{}.size() == 0); + BOOST_TEST(static_cstring<1>{"a"}.size() == 1); + BOOST_TEST(static_cstring<5>{"abc"}.size() == 3); + + // length(). + BOOST_TEST(static_cstring<0>{}.length() == 0); + BOOST_TEST(static_cstring<3>{"abc"}.length() == 3); + + // max_size(). + BOOST_TEST(static_cstring<0>{}.max_size() == 0); + BOOST_TEST(static_cstring<5>{"abc"}.max_size() == 5); + + // capacity(). + BOOST_TEST(static_cstring<0>{}.capacity() == 0); + BOOST_TEST(static_cstring<5>{"abc"}.capacity() == 5); +} + +static +void +testCStringClear() +{ + static_cstring<3> s("123"); + BOOST_TEST(!s.empty()); + s.clear(); + BOOST_TEST(s.empty()); + BOOST_TEST(s.size() == 0); + BOOST_TEST(*s.end() == 0); + BOOST_TEST(s == ""); +} + +static +void +testCStringPushPop() +{ + // push_back(). + { + static_cstring<5> s("abc"); + s.push_back('d'); + BOOST_TEST(s == "abcd"); + BOOST_TEST(s.size() == 4); + BOOST_TEST(*s.end() == 0); + + s.push_back('e'); + BOOST_TEST(s == "abcde"); + BOOST_TEST(s.size() == 5); + + BOOST_TEST_THROWS(s.push_back('f'), std::length_error); + } + + // pop_back(). + { + static_cstring<5> s("abcde"); + s.pop_back(); + BOOST_TEST(s == "abcd"); + BOOST_TEST(s.size() == 4); + BOOST_TEST(*s.end() == 0); + + s.pop_back(); + s.pop_back(); + s.pop_back(); + s.pop_back(); + BOOST_TEST(s.empty()); + } +} + +static +void +testCStringAppend() +{ + // append(const CharT* s). + { + static_cstring<12> s("Hello"); + s.append(", World"); + BOOST_TEST(s == "Hello, World"); + BOOST_TEST(*s.end() == 0); + } + { + static_cstring<5> s("abc"); + BOOST_TEST_THROWS(s.append("def"), std::length_error); + } + + // append(const CharT* s, size_type count) + { + static_cstring<10> s("abc"); + s.append("defgh", 3); + BOOST_TEST(s == "abcdef"); + BOOST_TEST(*s.end() == 0); + } + + // append(size_type count, CharT ch) + { + static_cstring<10> s("abc"); + s.append(3, 'x'); + BOOST_TEST(s == "abcxxx"); + BOOST_TEST(*s.end() == 0); + BOOST_TEST_THROWS(s.append(5, 'y'), std::length_error); + } + + // operator+=() + { + static_cstring<10> s("abc"); + s += "def"; + BOOST_TEST(s == "abcdef"); + s += 'g'; + BOOST_TEST(s == "abcdefg"); + BOOST_TEST(*s.end() == 0); + } +} + +static +void +testCStringComparison() +{ + // operator==() / operator!=(). + { + static_cstring<10> a("abc"); + static_cstring<10> b("abc"); + static_cstring<10> c("abd"); + + BOOST_TEST(a == b); + BOOST_TEST(!(a == c)); + BOOST_TEST(a != c); + BOOST_TEST(!(a != b)); + } + + // operator<(), <=(), >(), >=(). + { + static_cstring<10> a("abc"); + static_cstring<10> b("abd"); + + BOOST_TEST(a < b); + BOOST_TEST(a <= b); + BOOST_TEST(a <= a); + BOOST_TEST(b > a); + BOOST_TEST(b >= a); + BOOST_TEST(b >= b); + } + + // Comparison with CharT const*. + { + static_cstring<10> s("hello"); + BOOST_TEST(s == "hello"); + BOOST_TEST("hello" == s); + BOOST_TEST(s != "world"); + BOOST_TEST("world" != s); + } + + // compare(). + { + static_cstring<10> s("abc"); + BOOST_TEST(s.compare("abc") == 0); + BOOST_TEST(s.compare("abd") < 0); + BOOST_TEST(s.compare("abb") > 0); + } +} + +static +void +testCStringConversion() +{ + // operator string_view(). + { + static_cstring<10> s("hello"); + std::string_view sv = s; + BOOST_TEST(sv == "hello"); + BOOST_TEST(sv.size() == 5); + } + + // str(). + { + static_cstring<10> s("hello"); + std::string str = s.str(); + BOOST_TEST(str == "hello"); + BOOST_TEST(str.size() == 5); + } +} + +static +void +testCStringStream() +{ + static_cstring<10> s("hello"); + std::ostringstream oss; + oss << s; + BOOST_TEST(oss.str() == "hello"); +} + +static +void +testCStringSwap() +{ + static_cstring<10> a("hello"); + static_cstring<10> b("world"); + + a.swap(b); + + BOOST_TEST(a == "world"); + BOOST_TEST(b == "hello"); +} + +static +void +testCStringConstexpr() +{ + // constexpr construction and operations. + constexpr static_cstring<10> ce("test"); + static_assert(ce.size() == 4, ""); + static_assert(ce[0] == 't', ""); + static_assert(ce[1] == 'e', ""); + static_assert(ce[2] == 's', ""); + static_assert(ce[3] == 't', ""); + static_assert(!ce.empty(), ""); + static_assert(ce.max_size() == 10, ""); + static_assert(ce.capacity() == 10, ""); + + constexpr static_cstring<5> ce_empty; + static_assert(ce_empty.empty(), ""); + static_assert(ce_empty.size() == 0, ""); +} + +// Helper struct to test NTTP. +template +struct NTTPHelper +{ + static constexpr std::size_t size() noexcept + { + return S.size(); + } + + static constexpr const char* c_str() noexcept + { + return S.c_str(); + } +}; + +static +void +testCStringNTTP() +{ + // Test non-type template parameter usage (C++20). + + // Test size deduction from string literals. + BOOST_TEST(NTTPHelper<"hello">::size() == 5); + BOOST_TEST(NTTPHelper<"">::size() == 0); + BOOST_TEST(NTTPHelper<"test">::size() == 4); + + // Test that different strings create different types + constexpr bool same_type = std::is_same_v< + NTTPHelper<"hello">, + NTTPHelper<"hello">>; + BOOST_TEST(same_type); + + constexpr bool different_type = !std::is_same_v< + NTTPHelper<"hello">, + NTTPHelper<"world">>; + BOOST_TEST(different_type); + + // Test constexpr access. + constexpr std::size_t len = NTTPHelper<"constexpr">::size(); + static_assert(len == 9, ""); +} + +static +void +testCStringPODUsage() +{ + // Test usage in POD types (the original motivation). + struct UserRecord + { + int id; + static_cstring<63> name; + unsigned int flags; + }; + + static_assert(sizeof(static_cstring<63>) == 64, ""); + static_assert(std::is_trivially_copyable::value, ""); + + UserRecord user{}; + user.id = 42; + user.name = "Alice"; + user.flags = 0xFF; + + BOOST_TEST(user.id == 42); + BOOST_TEST(user.name.size() == 5); + BOOST_TEST(user.name == "Alice"); + BOOST_TEST(user.flags == 0xFF); + + // Copy the struct. + UserRecord copy = user; + BOOST_TEST(copy.name == "Alice"); + + // memcpy() should work. + UserRecord memcpy_dest; + std::memcpy(&memcpy_dest, &user, sizeof(UserRecord)); + BOOST_TEST(memcpy_dest.name == "Alice"); +} + +static +void +testCStringLargeCapacity() +{ + // Test strings with N > UCHAR_MAX (no remaining-capacity trick). + { + static_cstring<300> s; + BOOST_TEST(s.empty()); + BOOST_TEST(s.size() == 0); + + s = "This is a test string"; + BOOST_TEST(s.size() == 21); + BOOST_TEST(s == "This is a test string"); + + s.clear(); + BOOST_TEST(s.empty()); + } + + // Large string operations. + { + static_cstring<500> s; + s.assign(400, 'x'); + BOOST_TEST(s.size() == 400); + BOOST_TEST(s[0] == 'x'); + BOOST_TEST(s[399] == 'x'); + BOOST_TEST(*s.end() == 0); + } +} + +static +void +testCStringWideChar() +{ + // Test with wchar_t. + { + static_wcstring<10> ws; + BOOST_TEST(ws.empty()); + BOOST_TEST(ws.size() == 0); + } + + { + static_wcstring<10> ws(L"hello"); + BOOST_TEST(ws.size() == 5); + BOOST_TEST(ws[0] == L'h'); + BOOST_TEST(ws[4] == L'o'); + BOOST_TEST(*ws.end() == 0); + } +} + +int +runTests() +{ + testCStringSizeGuarantee(); + testCStringRemainingCapacityTrick(); + testCStringTypeTraits(); + testCStringConstruct(); + testCStringAssignment(); + testCStringElements(); + testCStringIterators(); + testCStringCapacity(); + testCStringClear(); + testCStringPushPop(); + testCStringAppend(); + testCStringComparison(); + testCStringConversion(); + testCStringStream(); + testCStringSwap(); + testCStringConstexpr(); + testCStringNTTP(); + testCStringPODUsage(); + testCStringLargeCapacity(); + testCStringWideChar(); + + return report_errors(); +} +} +} + +int +main() +{ + return boost::static_strings::runTests(); +}