This document records concepts used across STLSoft — structural
requirements that are not (necessarily) reified as language-level concepts
or base classes, but that several APIs assume. The first entry is the
Resizeable Buffer concept used by UnixSTL / WinSTL
system_traits and filesystem_traits overloads that accept a generic
buffer rather than a raw pointer and length.
A large class of operating-system and C library functions take a
destination buffer and a capacity (in characters or bytes), and
return either a written length or a failure code. Familiar examples
include getcwd / _getcwd, GetCurrentDirectory,
GetEnvironmentVariable, ExpandEnvironmentStrings, and
GetModuleFileName.
Callers typically need either:
- to fill a buffer they already own; or
- to discover how large a buffer is required before allocating;
Historical APIs do not share one contract. Common failure modes include:
- Fixed ceilings — e.g. relying solely on
_MAX_PATH/PATH_MAX, which is wrong for long paths and for systems without a compile-time maximum; - Truncation without a true demand — e.g. raw Windows'
GetModuleFileNamemay truncate into the supplied buffer and return a value equal to the capacity, without reporting the size that would have been sufficient (callers then grow-and-retry, or wrap the API); - Ambiguous “empty destination” — passing a non-null buffer with
capacity 0 is not a reliable size probe on many APIs: it often
means “write nothing into this empty buffer” and returns
0or fails, rather than returning the required length; - Inconsistent NUL character accounting — some returns count the
terminating
NUL, some do not, and some change meaning between the “too small” and “success” cases;
The normative, unambiguous pattern preferred in STLSoft facades is a two-phase protocol:
- Probe — call with a null buffer and a zero capacity
(
buff == nullptrandcch == 0). The function returns a demand: a character count of at least the full result length plus one for the terminatingNUL(it may return more than that minimum); - Allocate — obtain storage of at least that demand (stack, heap,
auto_buffer,vector, …); - Fill — call again with a non-null buffer and that capacity. On
success the return is the length of the string excluding the
NUL;
Preconditions on the raw overloads take the form:
0 == cch || nullptr != buff
so that a zero capacity is allowed only when the buffer pointer is null (the probe), and a non-null buffer always has a positive capacity for a fill.
Do not probe by passing a non-null buffer with length zero. That
conflates “empty destination” with “size query” and is not portable
across OS APIs. Prefer null buffer + zero length for the probe;
wrappers that must paper over defective native APIs (such as
GetModuleFileName) should still present this gold contract to their
callers.
This probe-vs-fill convention is the gold standard summarised in the discussion of required-length probing (Ask Brave — probe with 0 length vs null buffer).
Repeating probe / allocate / fill at every call site is verbose and easy to get wrong (especially NUL accounting and failure paths). STLSoft therefore offers overloads that take a resizeable buffer — any contiguous, resizable character sequence that can grow to the demanded size — and perform the two-phase protocol internally.
A type T models Resizeable Buffer for an element type CharT when
it provides at least:
| Member | Meaning |
|---|---|
value_type |
Element type (typically char or wchar_t); |
size_type |
Unsigned size type; |
size() const |
Number of elements currently managed including the trailing NUL when the buffer is used as a C-string; |
empty() const |
Equivalent to 0 == size(); |
operator[](size_type) |
Mutable access; contiguous storage so that &rb[0] addresses the first element when size() > 0; |
operator[](size_type) const |
Const access (needed by read-only helpers such as has_dir_end); |
resize(size_type) |
Change the number of managed elements. May return bool (true on success) or void (failure via exception or as documented for that type); |
Invariants assumed by path-oriented APIs:
- when
!empty(),rb[size() - 1] == CharT(0)(trailingNUL); resizepreserves existing elements up to the new size when shrinking or growing (so helpers such asensure_dir_endmay append in place);- storage is contiguous;
| Type | Models? | Notes |
|---|---|---|
stlsoft::auto_buffer<CharT> |
✅ | Preferred; resize returns bool; |
std::vector<CharT> |
✅ | resize returns void; use resizeable_buffer_resize; |
stlsoft::basic_path_buffer<…> |
❌ | size() excludes the NUL; off-by-one if passed as an rb; |
winstl::basic_file_path_buffer<…> |
❌ | resize is a no-op in some specialisations; |
Declared in
stlsoft/util/resizeable_buffer_helpers.hpp
(Doxygen on the file and on the function documents parameters, return
values, failure policy, and the expectation that callers set
ENOMEM / ERROR_OUTOFMEMORY themselves).
stlsoft::resizeable_buffer_resize(rb, newSize) invokes rb.resize(newSize)
whether that member returns bool or void, and whether resize is a
one-argument member or a two-argument member with a defaulted fill (as on
pre-C++11 std::vector), and always returns bool (true on success).
Prefer it over calling rb.resize directly in generic code.
Overloads of the form get_*(…, T_resizeableBuffer& rb) (and similar)
should follow this gold sequence:
- Probe via the raw overload with a null buffer and zero capacity;
- If the probe returns
0(failure) — leaverbunchanged, do not take&rb[0], return0; - If
rb.size() < cchRequired— callresizeable_buffer_resize(rb, cchRequired); on failure set an appropriate out-of-memory status (ENOMEM/ERROR_OUTOFMEMORY) and return0without taking&rb[0]on an empty buffer; - Fill via the raw overload into
&rb[0]with capacityrb.size(); - Optional out-parameters that point into the result (e.g. a basename separator pointer) are computed only after the final fill — never across a resize;
On success, the return value matches the raw fill overload (typically
the string length excluding the NUL). The buffer’s size() remains
the allocated demand (typically at least length + 1).
- Example:
examples/by_concept/resizeable_buffer/; - Pseudo-struct documentation in
unixstl::filesystem_traits/winstl::filesystem_traits(struct resizeable_bufferunder\name concepts); - Unit tests:
test/unit/stlsoft/util/resizeable_buffer_helpers/; - FAQ.md, INSTALL.md, EXAMPLES.md;