Compare commits

..

10 Commits

Author SHA1 Message Date
Gennaro Prota
e9313dc331 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.
2025-12-19 19:32:30 +01:00
Gennaro Prota
0c5e5b8e58 Fix our deduction guide 2025-12-19 18:26:40 +01:00
Gennaro Prota
6f0c00b268 Work around a Clang 3.7 bug affecting constexpr insert()
The iterator-based insert(const_iterator, size_type, value_type)
function relies on traits_type::move() to shift the existing null
terminator to its new position. Clang 3.7's constexpr evaluator does not
handle this correctly, causing the following test to fail:

  static_string<3>{"ab"}.insert(2, 1, 'c') == "abc"

Add an explicit term() call, guarded by a preprocessor conditional for
Clang 3.7, to ensure proper null termination.
2025-12-19 18:23:33 +01:00
Gennaro Prota
2175496c55 Condition the NTTP tests on __cpp_nontype_template_args
They were conditioned on detection of C++20 via __cplusplus, but Clang
10 and 11 don't support class types as NTTP, even though they report
C++20 via __cplusplus when -std=c++20 is used.
2025-12-19 18:23:33 +01:00
Gennaro Prota
aee3c62957 Apply the workaround in the previous commit to Clang 3.7 and 9-19, too
Reason: They have the same issue as GCC 9.
2025-12-19 18:23:33 +01:00
Gennaro Prota
0e28b358dc Work around GCC 9 rejecting a legitimate pointer comparison in a constexpr context 2025-12-19 18:23:33 +01:00
Gennaro Prota
4dcfb39494 Replace the implementation of cxper_char_traits::move() with a simpler one
Reason: See the new code comment.
2025-12-19 18:23:33 +01:00
Gennaro Prota
4bf6461ca1 Work around a bug in GCC 5-10
GCC 5-10 incorrectly complain about our nested classes being private.
So, make them public.
2025-12-19 18:23:33 +01:00
Krystian Stasiowski
67efdf6a9b Make basic_static_string usable as a NTTP 2025-12-19 18:23:33 +01:00
Krystian Stasiowski
3a410b8472 Add build directory and CMake preset files to .gitignore 2025-12-19 18:23:33 +01:00
4 changed files with 156 additions and 49 deletions

View File

@@ -25,14 +25,132 @@ on:
- meta/** - meta/**
- README.md - README.md
env:
B2_TARGETS: libs/$SELF/example
jobs: jobs:
call-boost-ci: linux:
name: Run Boost.CI runs-on: ubuntu-24.04
uses: boostorg/boost-ci/.github/workflows/reusable.yml@master strategy:
with: fail-fast: false
exclude_cxxstd: '98,03,0x,11,14,17' matrix:
enable_pr_coverage: false include:
enable_multiarch: false - { 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

View File

@@ -25,7 +25,7 @@ namespace static_strings {
namespace detail { namespace detail {
// Primary template: No remaining-capacity trick; uses traits::length() for size. // Primary template: No remaining-capacity trick; uses traits::length() for length.
template<std::size_t N, typename CharT, typename Traits, bool UseRemaining> template<std::size_t N, typename CharT, typename Traits, bool UseRemaining>
class static_cstring_base class static_cstring_base
{ {
@@ -46,17 +46,12 @@ public:
data_[sz] = value_type{}; data_[sz] = value_type{};
} }
constexpr void init_empty() noexcept
{
data_[0] = value_type{};
}
// Defaulted comparisons for structural type support. // Defaulted comparisons for structural type support.
constexpr bool operator==(const static_cstring_base&) const noexcept = default; constexpr bool operator==(const static_cstring_base&) const noexcept = default;
constexpr auto operator<=>(const static_cstring_base&) const noexcept = default; constexpr auto operator<=>(const static_cstring_base&) const noexcept = default;
}; };
// Specialization for N <= UCHAR_MAX: Use remaining-capacity trick. // Specialization for N <= UCHAR_MAX: Uses remaining-capacity trick.
template<std::size_t N, typename CharT, typename Traits> template<std::size_t N, typename CharT, typename Traits>
class static_cstring_base<N, CharT, Traits, true> class static_cstring_base<N, CharT, Traits, true>
{ {
@@ -78,12 +73,6 @@ public:
data_[N] = static_cast<value_type>(N - sz); data_[N] = static_cast<value_type>(N - sz);
} }
constexpr void init_empty() noexcept
{
data_[0] = value_type{};
data_[N] = static_cast<value_type>(N);
}
// Defaulted comparisons for structural type support. // Defaulted comparisons for structural type support.
constexpr bool operator==(const static_cstring_base&) const noexcept = default; constexpr bool operator==(const static_cstring_base&) const noexcept = default;
constexpr auto operator<=>(const static_cstring_base&) const noexcept = default; constexpr auto operator<=>(const static_cstring_base&) const noexcept = default;
@@ -100,7 +89,6 @@ public:
using base::data_; using base::data_;
using base::get_size; using base::get_size;
using base::set_size; using base::set_size;
using base::init_empty;
// Member types // Member types
using traits_type = Traits; using traits_type = Traits;
@@ -120,7 +108,7 @@ public:
// Constructors. // Constructors.
constexpr basic_static_cstring() noexcept constexpr basic_static_cstring() noexcept
{ {
init_empty(); set_size(0);
} }
constexpr basic_static_cstring(const CharT* s) constexpr basic_static_cstring(const CharT* s)
@@ -201,21 +189,25 @@ public:
constexpr reference front() noexcept constexpr reference front() noexcept
{ {
BOOST_STATIC_STRING_ASSERT(!empty());
return data_[0]; return data_[0];
} }
constexpr const_reference front() const noexcept constexpr const_reference front() const noexcept
{ {
BOOST_STATIC_STRING_ASSERT(!empty());
return data_[0]; return data_[0];
} }
constexpr reference back() noexcept constexpr reference back() noexcept
{ {
BOOST_STATIC_STRING_ASSERT(!empty());
return data_[size() - 1]; return data_[size() - 1];
} }
constexpr const_reference back() const noexcept constexpr const_reference back() const noexcept
{ {
BOOST_STATIC_STRING_ASSERT(!empty());
return data_[size() - 1]; return data_[size() - 1];
} }
@@ -268,7 +260,7 @@ public:
// Modifiers. // Modifiers.
constexpr void clear() noexcept constexpr void clear() noexcept
{ {
init_empty(); set_size(0);
} }
constexpr basic_static_cstring& assign(const CharT* s) constexpr basic_static_cstring& assign(const CharT* s)

View File

@@ -75,41 +75,39 @@ testCStringRemainingCapacityTrick()
large.assign(100, 'x'); large.assign(100, 'x');
BOOST_TEST(large.size() == 100); BOOST_TEST(large.size() == 100);
BOOST_TEST(large.capacity() == UCHAR_MAX); BOOST_TEST(large.capacity() == UCHAR_MAX);
BOOST_TEST(static_cast<unsigned char>(large.data()[UCHAR_MAX]) == (large.capacity() - large.size())); BOOST_TEST(static_cast<unsigned char>(large.data()[UCHAR_MAX]) == (UCHAR_MAX - large.size()));
} }
} }
template<typename S>
struct CStringTypeTraits
{
static_assert(std::is_trivially_copyable<S>::value);
static_assert(std::is_trivially_copy_constructible<S>::value);
static_assert(std::is_trivially_move_constructible<S>::value);
static_assert(std::is_trivially_copy_assignable<S>::value);
static_assert(std::is_trivially_move_assignable<S>::value);
static_assert(std::is_trivially_destructible<S>::value);
};
static static
void void
testCStringTypeTraits() testCStringTypeTraits()
{ {
// static_cstring should be trivially copyable for use in POD structs.
{ {
using S = static_cstring<0>; using S = static_cstring<0>;
static_assert(std::is_trivially_copyable<S>::value, ""); CStringTypeTraits< S > check;
static_assert(std::is_trivially_copy_constructible<S>::value, ""); static_cast<void>(check);
static_assert(std::is_trivially_move_constructible<S>::value, "");
static_assert(std::is_trivially_copy_assignable<S>::value, "");
static_assert(std::is_trivially_move_assignable<S>::value, "");
static_assert(std::is_trivially_destructible<S>::value, "");
} }
{ {
using S = static_cstring<63>; using S = static_cstring<63>;
static_assert(std::is_trivially_copyable<S>::value, ""); CStringTypeTraits< S > check;
static_assert(std::is_trivially_copy_constructible<S>::value, ""); static_cast<void>(check);
static_assert(std::is_trivially_move_constructible<S>::value, "");
static_assert(std::is_trivially_copy_assignable<S>::value, "");
static_assert(std::is_trivially_move_assignable<S>::value, "");
static_assert(std::is_trivially_destructible<S>::value, "");
} }
{ {
using S = static_cstring<300>; using S = static_cstring<300>;
static_assert(std::is_trivially_copyable<S>::value, ""); CStringTypeTraits< S > check;
static_assert(std::is_trivially_copy_constructible<S>::value, ""); static_cast<void>(check);
static_assert(std::is_trivially_move_constructible<S>::value, "");
static_assert(std::is_trivially_copy_assignable<S>::value, "");
static_assert(std::is_trivially_move_assignable<S>::value, "");
static_assert(std::is_trivially_destructible<S>::value, "");
} }
} }

View File

@@ -72,9 +72,8 @@ struct cxper_char_traits
// This implementation does not handle overlapping ranges where // This implementation does not handle overlapping ranges where
// dest > src. A correct implementation would need to detect this // dest > src. A correct implementation would need to detect this
// case and copy backwards, but detecting overlap requires pointer // case and copy backwards, but detecting overlap requires pointer
// comparisons that are not allowed in constant expressions when // comparisons that many of the tested compiles (incorrectly) refuse
// the pointers point to unrelated objects (e.g., a string literal // in constant expressions.
// and the static_string's internal buffer).
// //
// Since cxper_char_traits is only used for testing constexpr // Since cxper_char_traits is only used for testing constexpr
// functionality and the tests do not exercise overlapping moves // functionality and the tests do not exercise overlapping moves