Coverage Report

Created: 2026-08-13 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/stdplus/include/stdplus/str/buf.hpp
Line
Count
Source
1
#pragma once
2
3
#include <bit>
4
#include <cstdint>
5
#include <limits>
6
#include <memory>
7
#include <string_view>
8
9
namespace stdplus
10
{
11
12
namespace detail
13
{
14
15
template <typename CharT, std::size_t BufLen, auto Endian = std::endian::native>
16
union StrBufStore;
17
18
template <typename CharT, std::size_t BufLen>
19
union StrBufStore<CharT, BufLen, std::endian::little>
20
{
21
    struct Inl
22
    {
23
        CharT ptr[BufLen];
24
        std::make_unsigned_t<CharT> len;
25
        static_assert(BufLen <= std::numeric_limits<decltype(len)>::max() / 2);
26
    } inl;
27
    struct Dyn
28
    {
29
        std::uint8_t rsvd[offsetof(Inl, len) + 1 - sizeof(CharT*) -
30
                          sizeof(std::size_t) * 2];
31
        CharT* ptr;
32
        std::size_t cap;
33
        std::size_t len;
34
    } dyn;
35
    static_assert(offsetof(Inl, len) + sizeof(Inl::len) ==
36
                  offsetof(Dyn, len) + sizeof(Dyn::len));
37
};
38
39
template <typename CharT, std::size_t BufLen>
40
union StrBufStore<CharT, BufLen, std::endian::big>
41
{
42
    struct Inl
43
    {
44
        std::make_unsigned_t<CharT> len;
45
        CharT ptr[BufLen];
46
        static_assert(BufLen <= std::numeric_limits<decltype(len)>::max() / 2);
47
    } inl;
48
    struct Dyn
49
    {
50
        std::size_t len;
51
        std::size_t cap;
52
        CharT* ptr;
53
    } dyn;
54
};
55
56
template <typename CharT, std::size_t ObjSize, typename Allocator>
57
struct StrBufAS : Allocator
58
{
59
    static inline constexpr std::size_t buf_len =
60
        (ObjSize -
61
         sizeof(std::declval<detail::StrBufStore<CharT, 63>>().inl.len) -
62
         (std::is_empty_v<Allocator> ? 0 : sizeof(Allocator))) /
63
        sizeof(CharT);
64
    using Store = detail::StrBufStore<CharT, buf_len>;
65
    static_assert(sizeof(std::declval<Store>().inl) >=
66
                  sizeof(std::declval<Store>().dyn));
67
68
    static inline constexpr auto dyn_mask =
69
        std::size_t{1} << ((sizeof(std::size_t) << 3) - 1);
70
    static inline constexpr auto inl_mask =
71
        decltype(Store::Inl::len){1} << ((sizeof(Store::Inl::len) << 3) - 1);
72
73
    Store store;
74
75
    constexpr bool isDyn() const noexcept
76
90.0k
    {
77
90.0k
        if (std::is_constant_evaluated())
78
0
        {
79
0
            return true;
80
0
        }
81
90.0k
        return store.inl.len & inl_mask;
82
90.0k
    }
83
84
    constexpr std::size_t dynLen() const noexcept
85
0
    {
86
0
        return store.dyn.len & ~dyn_mask;
87
0
    }
88
89
    constexpr void dynLen(std::size_t len) noexcept
90
0
    {
91
0
        store.dyn.len = len | dyn_mask;
92
0
    }
93
94
    constexpr auto inlLen() const noexcept
95
87.2k
    {
96
87.2k
        return store.inl.len;
97
87.2k
    }
98
99
    constexpr void inlLen(decltype(Store::Inl::len) len) noexcept
100
87.2k
    {
101
87.2k
        store.inl.len = len;
102
87.2k
    }
103
104
    constexpr CharT* allocate(std::size_t n)
105
0
    {
106
0
        auto ptr = static_cast<Allocator&>(*this).allocate(n);
107
0
        if (std::is_constant_evaluated())
108
0
        {
109
0
            for (std::size_t i = 0; i < n; ++i)
110
0
            {
111
0
                std::construct_at(ptr + i);
112
0
            }
113
0
        }
114
0
        return ptr;
115
0
    }
116
117
    constexpr void zeroInit() noexcept
118
1.39k
    {
119
1.39k
        if (std::is_constant_evaluated())
120
0
        {
121
0
            store.dyn.ptr = allocate(buf_len);
122
0
            store.dyn.cap = buf_len;
123
0
            dynLen(0);
124
0
        }
125
1.39k
        else
126
1.39k
        {
127
1.39k
            inlLen(0);
128
1.39k
        }
129
1.39k
    }
130
131
1.39k
    constexpr StrBufAS(Allocator&& a) noexcept : Allocator(std::move(a))
132
1.39k
    {
133
1.39k
        if (std::is_constant_evaluated())
134
0
        {
135
0
            std::construct_at(&store.dyn);
136
0
        }
137
1.39k
        zeroInit();
138
1.39k
    }
139
140
    constexpr StrBufAS& operator=(Allocator&& a) noexcept
141
    {
142
        *static_cast<Allocator*>(this) = a;
143
        return *this;
144
    }
145
};
146
147
} // namespace detail
148
149
template <typename CharT, std::size_t ObjSize = 128,
150
          typename Allocator = std::allocator<CharT>>
151
class BasicStrBuf
152
{
153
  private:
154
    detail::StrBufAS<CharT, ObjSize, Allocator> as;
155
156
    template <bool assign>
157
    constexpr void inlcopy(const BasicStrBuf& other) noexcept
158
    {
159
        const auto optr = other.as.store.inl.ptr;
160
        const auto olen = other.as.inlLen();
161
        if (assign || std::is_constant_evaluated())
162
        {
163
            if (as.isDyn())
164
            {
165
                as.dynLen(olen);
166
                std::copy(optr, optr + olen, as.store.dyn.ptr);
167
                return;
168
            }
169
        }
170
        as.store.inl.len = other.as.store.inl.len;
171
        std::copy(optr, optr + olen, as.store.inl.ptr);
172
    }
173
174
    template <bool assign>
175
    constexpr void move(BasicStrBuf&& other) noexcept
176
    {
177
        if (!other.as.isDyn())
178
        {
179
            inlcopy<assign>(other);
180
            other.as.zeroInit();
181
            return;
182
        }
183
        if (assign || std::is_constant_evaluated())
184
        {
185
            if (as.isDyn())
186
            {
187
                as.deallocate(as.store.dyn.ptr, as.store.dyn.cap);
188
            }
189
        }
190
        as.store.dyn = other.as.store.dyn;
191
        other.as.zeroInit();
192
    }
193
194
    template <bool assign>
195
    constexpr void copy(const BasicStrBuf& other)
196
    {
197
        if (!other.as.isDyn())
198
        {
199
            inlcopy<assign>(other);
200
            return;
201
        }
202
        const auto optr = other.as.store.dyn.ptr;
203
        const std::size_t olen = other.as.dynLen();
204
        if (assign || std::is_constant_evaluated())
205
        {
206
            if (as.isDyn())
207
            {
208
                if (olen <= as.store.dyn.cap)
209
                {
210
                    as.store.dyn.len = other.as.store.dyn.len;
211
                    std::copy(optr, optr + olen, as.store.dyn.ptr);
212
                    return;
213
                }
214
                else
215
                {
216
                    as.deallocate(as.store.dyn.ptr, as.store.dyn.cap);
217
                }
218
            }
219
        }
220
        if (olen <= as.buf_len)
221
        {
222
            std::copy(optr, optr + olen, as.store.inl.ptr);
223
            as.inlLen(olen);
224
            return;
225
        }
226
        as.store.dyn.cap = other.as.store.dyn.cap;
227
        as.store.dyn.ptr = as.allocate(as.store.dyn.cap);
228
        as.store.dyn.len = other.as.store.dyn.len;
229
        std::copy(optr, optr + olen, as.store.dyn.ptr);
230
    }
231
232
  public:
233
    using value_type = CharT;
234
235
1.39k
    constexpr BasicStrBuf() noexcept : as({}) {}
236
237
    constexpr BasicStrBuf(BasicStrBuf&& other) noexcept :
238
        as(static_cast<Allocator&&>(other.as))
239
    {
240
        move</*assign=*/false>(std::move(other));
241
    }
242
243
    constexpr BasicStrBuf(const BasicStrBuf& other) :
244
        as(std::allocator_traits<Allocator>::
245
               select_on_container_copy_construction(
246
                   static_cast<const Allocator&>(other.as)))
247
    {
248
        copy</*assign=*/false>(other);
249
    }
250
251
    constexpr BasicStrBuf& operator=(BasicStrBuf&& other) noexcept
252
    {
253
        if (this != &other)
254
        {
255
            if constexpr (typename std::allocator_traits<Allocator>::
256
                              propagate_on_container_move_assignment())
257
            {
258
                as = static_cast<Allocator&&>(other.as);
259
            }
260
            move</*assign=*/true>(std::move(other));
261
        }
262
        return *this;
263
    }
264
265
    constexpr BasicStrBuf& operator=(const BasicStrBuf& other)
266
    {
267
        if (this != &other)
268
        {
269
            if constexpr (typename std::allocator_traits<Allocator>::
270
                              propagate_on_container_copy_assignment())
271
            {
272
                as = static_cast<const Allocator&>(other.as);
273
            }
274
            copy</*assign=*/true>(other);
275
        }
276
        return *this;
277
    }
278
279
    constexpr ~BasicStrBuf()
280
1.39k
    {
281
1.39k
        if (as.isDyn())
282
0
        {
283
0
            as.deallocate(as.store.dyn.ptr, as.store.dyn.cap);
284
0
        }
285
1.39k
    }
286
287
    constexpr std::size_t size() const noexcept
288
1.39k
    {
289
1.39k
        return as.isDyn() ? as.dynLen() : as.inlLen();
290
1.39k
    }
291
292
    constexpr operator std::basic_string_view<CharT>() const noexcept
293
1.39k
    {
294
1.39k
        return std::basic_string_view<CharT>(begin(), size());
295
1.39k
    }
296
297
    constexpr CharT* append(std::size_t amt)
298
85.8k
    {
299
85.8k
        if (!as.isDyn())
300
85.8k
        {
301
85.8k
            const std::size_t oldlen = as.inlLen();
302
85.8k
            const std::size_t newlen = oldlen + amt;
303
85.8k
            if (newlen <= as.buf_len)
304
85.8k
            {
305
85.8k
                as.inlLen(newlen);
306
85.8k
                return as.store.inl.ptr + oldlen;
307
85.8k
            }
308
0
            const std::size_t newcap = newlen + (newlen >> 1);
309
0
            const auto ptr = as.allocate(newcap);
310
0
            std::copy(as.store.inl.ptr, as.store.inl.ptr + oldlen, ptr);
311
312
0
            as.store.dyn.ptr = ptr;
313
0
            as.store.dyn.cap = newcap;
314
0
            as.dynLen(newlen);
315
0
            return ptr + oldlen;
316
85.8k
        }
317
0
        const std::size_t oldlen = as.dynLen();
318
0
        const std::size_t newlen = oldlen + amt;
319
0
        if (newlen > as.store.dyn.cap)
320
0
        {
321
0
            const std::size_t newcap = newlen + (newlen >> 1);
322
0
            const auto ptr = as.allocate(newcap);
323
0
            std::copy(as.store.dyn.ptr, as.store.dyn.ptr + oldlen, ptr);
324
0
            as.deallocate(as.store.dyn.ptr, as.store.dyn.cap);
325
326
0
            as.store.dyn.ptr = ptr;
327
0
            as.store.dyn.cap = newcap;
328
0
        }
329
0
        as.dynLen(newlen);
330
0
        return as.store.dyn.ptr + oldlen;
331
85.8k
    }
332
333
    constexpr void append(const CharT* begin, const CharT* end)
334
    {
335
        std::copy(begin, end, append(end - begin));
336
    }
337
338
    constexpr void push_back(CharT c) noexcept
339
85.8k
    {
340
85.8k
        *append(1) = c;
341
85.8k
    }
342
343
    constexpr void shrink(std::size_t amt) noexcept
344
    {
345
        if (as.isDyn())
346
        {
347
            as.store.dyn.len -= amt;
348
        }
349
        else
350
        {
351
            as.store.inl.len -= amt;
352
        }
353
    }
354
355
    constexpr void clear() noexcept
356
    {
357
        if (as.isDyn())
358
        {
359
            as.dynLen(0);
360
        }
361
        else
362
        {
363
            as.inlLen(0);
364
        }
365
    }
366
367
    constexpr CharT* data() noexcept
368
    {
369
        return as.isDyn() ? as.store.dyn.ptr : as.store.inl.ptr;
370
    }
371
372
    constexpr const CharT* data() const noexcept
373
1.39k
    {
374
1.39k
        return as.isDyn() ? as.store.dyn.ptr : as.store.inl.ptr;
375
1.39k
    }
376
377
    constexpr CharT* begin() noexcept
378
    {
379
        return data();
380
    }
381
382
    constexpr const CharT* begin() const noexcept
383
1.39k
    {
384
1.39k
        return data();
385
1.39k
    }
386
387
    constexpr CharT* end() noexcept
388
    {
389
        return begin() + size();
390
    }
391
392
    constexpr const CharT* end() const noexcept
393
    {
394
        return begin() + size();
395
    }
396
397
    constexpr const CharT* cbegin() const noexcept
398
    {
399
        return begin();
400
    }
401
402
    constexpr const CharT* cend() const noexcept
403
    {
404
        return end();
405
    }
406
407
    constexpr bool operator==(
408
        std::basic_string_view<CharT> other) const noexcept
409
    {
410
        return std::basic_string_view<CharT>{*this} == other;
411
    }
412
};
413
414
static_assert(sizeof(BasicStrBuf<char, 128>) == 128);
415
static_assert(sizeof(BasicStrBuf<wchar_t, 256>) == 256);
416
417
using StrBuf = BasicStrBuf<char>;
418
using WStrBuf = BasicStrBuf<wchar_t>;
419
420
} // namespace stdplus