...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
This is the constraint on the Derived
template parameter to iterator_interface
, view_interface
and sequence_container_interface
:
std::enable_if_t< std::is_class<Derived>::value && std::is_same<Derived, std::remove_cv_t<Derived>>::value>
This prevents instantiating an interface template with an int
,
a const
type, a reference type,
etc.
Further constraints are not possible (for instance, that view_interface
is given a Derived
template parameter for a type that
has a begin()
and end()
),
because Derived
is an incomplete
type within each *_interface
template.
struct
The interface templates rely mostly on public members provided by their Derived
template parameter. However, iterator_interface
requires you to supply base_reference()
functions if you want it to act like an adaptor.
Since at least the non-const
overload
provides a non-const
lvalue reference
to one of your types data members, it will break the encapsulation of many
types to leave base_reference()
a public member. To allow users to keep these
overloads private, access
exists.
There wouldn't be much point in adding this functionality to view_interface
, because it only
uses the begin()
and end()
of the Derived
type anyway.
For sequence_container_interface
it also does not make much sense. Consider how many container adaptors you've
written. That's a use case that does not come up often.
iterator_interface
does in fact
take a lot of template parameters. However, it usually only takes three: the
Derived
type, the iterator
category, and the iterator's value_type
.
When you make a proxy iterator, you typically use the proxy_iterator_interface
alias,
and you again only need the same three template parameters. Though you can
opt into more template parameters, the rest are seldom necessary.
By contrast, the view_interface
and sequence_container_interface
templates have very few template parameters. For view_interface
, this is because
there are no member typedefs in the view
concept. For sequence_container_interface
,
it was deemed ridiculous to create a template whose purpose is to reduce code
size, which takes 14 template parameters.
iterator
sequence_container_interface
could deduce some of the nested types required for a standard sequence container.
For instance, iterator
can
be deduced as decltype(*begin())
.
However, a type D
derived from
sequence_container_interface
may need to use some of these nested types — like iterator
— in its interface or implementation. If this is the case, those nested
types are not available early enough in the parse to be used in D
, if they come from deductions in sequence_container_interface
.
This leaves the user in the awkward position of defining the same nested type
with a different name that can be used within D
.
It seems better to leave these types for the user to define.
That's right. Associative containers have an interface that assumes that they are node-based containers. On modern hardware, node-based containers are not very efficient, and I don't want to encourage people to write more of them. Unordered associative containers have an interface that precludes open addressing. I don't want to encourage more of that either.
It may not be immediately obvious, but sequence_container_interface
simply cannot help with the allocator-aware requirements. All of the allocator-aware
requirements but 3 are special members and constructors. A CRTP
base template is unable to provide those, based on the language rules. That
leaves the allocator_type
typedef,
which the user must provide; member swap()
, which is already a container requirement
(the allocator-aware table entry just specifies that member swap()
must be constant-time); and get_allocator()
,
which again is something the user must provide.
Most of the difficulty of dealing with allocators has to do with the implementation
details of their use within your container. sequence_container_interface
provides missing elements of a sequence container's interface, by calling user-provided
members of that same interface. It cannot help you with your container's implementation.