inplace_foo versus fixed_capacity_foo
inplace_foo versus fixed_capacity_fooSomeone 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_backmight throw an exception;bounded_string::appendmight truncate on overflow;bounded_concurrent_queue::push_backmight 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 flooks 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.
