inplace_foo
versus fixed_capacity_foo
inplace_foo
versus fixed_capacity_foo
Someone on Slack just asked, “What is the proper name for my fixed-capacity string class?” I’ve thought about this question before, which is unsurprising for someone active in SG14 where non-allocating containers are all the rage. In October 2017 I wrote:
It occurs to me that we must distinguish between two kinds of “in-place-ness”:
(1)
inplace_function
,inplace_unique_function
,inplace_any
, etc.: Has a fixed-size buffer for type-erasure. Requires template parameters “Size
” and “Align
”. Does not require any “overflow” behavior, because if the user attempts to store a too-big or too-aligned object into the buffer, we static-assert failure. Storing a too-big object is just as much a compile-time failure as storing a non-callable or non-copyable object.(2)
bounded_string
,bounded_vector
,bounded_concurrent_queue
, etc.: Has a fixed-size array of non-type-erased elements. Requires template parameters “T
” and “Size
”; the appropriate alignment is completely determined byalignof(T)
. Requires the specification of some particular “overflow” behavior, because it is part of the contract of this type that it can gain or lose elements. For example,bounded_vector::push_back
might throw an exception;bounded_string::append
might truncate on overflow;bounded_concurrent_queue::push_back
might block.I strongly prefer the term “inplace” for the former (type-erased) category. I strongly prefer that the former (type-erased) category be differentiated from the latter (non-type-erased) category. I tentatively prefer the term “bounded” for the latter category. I am not sure that the latter category is all one category; it might yet be several categories mashed together.
My opinions on the subject have not changed, except that I now more-than-tentatively prefer the term “fixed-capacity” for the latter category. Thus:
template<class Sig, size_t Cap, size_t Align>
class inplace_function;
template<size_t Cap, size_t Align>
class inplace_any;
template<size_t Cap /*,class OverflowPolicy?*/>
class fixed_capacity_string;
template<class T, size_t Cap /*,class OverflowPolicy?*/>
class fixed_capacity_vector;
template<class T, size_t Cap /*,class OverflowPolicy?*/>
class fixed_capacity_queue;
In the fixed_capacity_string
and fixed_capacity_vector
cases, the only sensible
overflow policy IMHO is “undefined behavior,” i.e., a runtime assertion failure.
In the case of fixed_capacity_queue
, I could see the desire for ringbuffer-like
behavior — just quietly drop the element at the front of the queue and keep going.
However, that could reasonably be considered a new kind of container — say,
fixed_capacity_ringbuffer
— instead of a parameterization of the “queue” container.
Notice that the Boost project’s naming convention for the latter category is
not fixed_capacity_foo
but rather static_foo
, as in
static_string<Cap>
and static_vector<T, Cap>
.
I should explain that I don’t like that naming convention for two reasons:
-
The defining characteristic of a fixed-capacity foo is that its overflow behavior is dynamic; you can add or remove elements at runtime; it cannot be static-asserted upon. So emphasizing “static” in this context sends precisely the wrong message.
-
The word “static” is already overloaded about five different ways in C++, and we don’t need to add any more. In particular,
static_foo f
looks a heck of a lot likestatic foo f
, and I can imagine people getting very confused about the meaning ofthread_local static_foo f
.
fixed_capacity_foo
suffers from neither of these disadvantages; its only
disadvantage is that it’s verbose. But C++ could use a little more verbosity
sometimes. And
template<class T> using fcv10 = fixed_capacity_vector<T, 10>
is always available to the masochists.