To put it simply, the C++ specification is deficient when it comes to defining iterator concepts, because it conflates the traversal and value access concerns. This is fully described here:
http://www.boost.org/doc/libs/1_62_0/libs/iterator/doc/new-iter-concepts.html
I propose that the networking proposal break new ground and push for addressing this defect. Specifically, for the ConstBufferSequence and MutableBufferSequence type requirements, replace the BidirectionalIterator requirement on const_iterator
with the dual requirements ReadableIterator and BidirectionalTraversalIterator, defined here:
http://www.boost.org/doc/libs/1_62_0/libs/iterator/doc/new-iter-concepts.html#readable-iterators-lib-readable-iterators
Rationale: This allows more flexible operations on buffer sequences, such as applying range transformations to the value type. Without this change, implementations that transform buffer sequences (such as concatenating them, adjusting the byte range they represent, or converting from mutable to const sequences) become overly complex, inefficient, or impossible.
Here are examples of buffer sequence transformations in use today which would be disallowed by the current BidirectionalIterator requirements in the networking TS:
https://github.com/vinniefalco/Beast/blob/70b8555cdca69b9c5777db02115d30d1c1aad631/include/beast/core/buffer_cat.hpp#L22
https://github.com/vinniefalco/Beast/blob/70b8555cdca69b9c5777db02115d30d1c1aad631/include/beast/core/buffers_adapter.hpp#L17
https://github.com/vinniefalco/Beast/blob/70b8555cdca69b9c5777db02115d30d1c1aad631/include/beast/core/consuming_buffers.hpp#L20
https://github.com/vinniefalco/Beast/blob/70b8555cdca69b9c5777db02115d30d1c1aad631/include/beast/core/prepare_buffers.hpp#L135
One implementation above, buffer_cat
, is particularly useful as it allows multiple system calls (to read or write on a socket) to be reduced to one. Consider a network protocol that sends objects which have multiple parts, such as a message header followed by a message body. The header and body could each be represented by a ConstBufferSequence. buffer_cat
allows sending the entire message in a single system call instead of doing it in two separate calls. This has been used in the wild to achieve impressive decreases in latency for a HTTP server. The current implementation of buffer_cat
is rather elegant, it uses the equivalent of a std::variant
to efficiently transform the inputs without making a copy of the sequences:
https://github.com/vinniefalco/Beast/blob/70b8555cdca69b9c5777db02115d30d1c1aad631/include/beast/core/detail/buffer_cat.hpp#L22
This implementation is unfortunately non conforming because the iterators it defines, do not meet the requirements of BidirectionalIterator since they return an rvalue:
https://github.com/vinniefalco/Beast/blob/70b8555cdca69b9c5777db02115d30d1c1aad631/include/beast/core/detail/buffer_cat.hpp#L103
This was done because buffer_cat
allows conversion from mutable sequences to const sequences. For example, if a
and b
are **ConstBufferSequence* objects, and c
is a MutableBufferSequence, the expression buffer_cat(a, b, c)
returns an object representing the concatenation of a
, b
, and c
, whose type meets the requirements of ConstBufferSequence.
The alternative to the elegant, current implementation of buffer_cat
is one that uses a std::vector
equivalent to store a precomputed version of the concatenated sequence. Its true that a specialization could make this vector implementation required in only a subset of use cases but the solution is non-ideal.
For this reason, I urge the authors to consider requiring only ReadableIterator and BidirectionalTraversalIterator for iterators representing buffer sequences.