<!DOCTYPE html>

<html>
<head>
    <meta charset="utf-8">
    <title>Efficient composition with DynamicBuffer</title>
    <style type="text/css">
    p {text-align:justify}
    li {text-align:justify}
    blockquote.note
    {
        background-color:#E0E0E0;
        padding-left: 15px;
        padding-right: 15px;
        padding-top: 1px;
        padding-bottom: 1px;
    }
    p.note
    {
        background-color:#E0E0E0;
        padding-left: 15px;
        padding-right: 15px;
        padding-top: 1px;
        padding-bottom: 1px;
    }
    ins { background-color: #CCFFCC; }
    del { background-color: #FFCCCC; }
    .insert { background-color: #CCFFCC; }
    address {text-align:right;}
    h1 {text-align:center;}
    span.comment {color:#C80000;}
    code {white-space:pre;}
    </style>
</head>
<body>
<pre>
Doc. no:  P1100R0
Audience: LEWG
Date:     2018-06-18
Reply-To: Vinnie Falco (<a href="mailto:vinnie.falco@gmail.com">vinnie.falco@gmail.com</a>)
</pre>
<hr>
<h1>Efficient composition with DynamicBuffer</h1>

<h2>Contents</h2>

<ul>
<li><a href="#Introduction">Introduction</a></li>
<li><a href="#Description">Description</a>
    <ul>
    <li><a href="#Async">Asynchronous Operations</a>
    <li><a href="#Composition">Problem with Composition</a>
    <li><a href="#Exception">Problem with Exceptions</a>
    <li><a href="#Workarounds">Problem with Workarounds</a>
    </ul>
</li>
<li><a href="#Solution">Solution</a></li>
<li><a href="#References">References</a></li>
</ul>

<a name="Introduction"></a><h2>Introduction</h2>
<p>
This paper describes a design flaw in the specification of the
<b>DynamicBuffer</b> concept found in [networking.ts], which prevents efficient
composition of asynchronous operations accepting an instance of a dynamic
buffer. The proposed remedy reverts back some of the differences in the TS from
its precursor library Asio (and Boost.Asio) by making <b>DynamicBuffer</b> a
true storage type.
</p>

<a name="Description"></a><h2>Description</h2>

<p>
Most network algorithms accept fixed-size buffers as input, output, or both,
in their signatures and contracts. For operations where the caller cannot easily
determine ahead of time the storage requirements needed for an algorithm to
meet its post-conditions, [networking.ts] introduces the <b>DynamicBuffer</b> concept:
</p>
<blockquote>
A dynamic buffer encapsulates memory storage that may be automatically
resized as required, where the memory is divided into two regions: readable
bytes followed by writable bytes. [buffer.reqmts.dynamicbuffer]
</blockquote>
<p>
Readable and writable bytes returned by a dynamic buffer are represented
respectively by the <b>ConstBufferSequence</b> and <b>MutableBufferSequence</b>
concepts defined in the TS. These sequences are a special type of range whose
element type is a span of bytes. Network algorithms append data to the dynamic
buffer in two steps. First, a buffer sequence representing writable bytes is
obtained by calling <tt>prepare</tt>. Then, after the algorithm has placed zero
or more bytes at the beginning of the sequence the function <tt>commit</tt> is
called to move some or all of that data to the sequence representing the readable
bytes. The term "move" here is notional: the dynamic buffer concept does not
require (nor disallow) a memory move. The following code reads from a
socket into a dynamic buffer:
</p>
<blockquote><pre>
string s;
dynamic_string_buffer b{s}; // dynamic_string_buffer is defined by the TS
auto const bytes_transferred = socket.read_some(b.prepare(1024)); // read up to 1024 bytes
b.commit(bytes_transferred);
assert(s.size() == bytes_transferred);
</pre></blockquote>
<p>
As can be seen in the preceding code, an instance of <tt>dynamic_string_buffer</tt>
does not own its storage. Overwhelming feedback from users indicates they wish
to use traditional types like <tt>string</tt> or <tt>vector</tt> to store their
data. The TS improves on its predecessor Asio by providing the types
<tt>dynamic_string_buffer</tt> and <tt>dynamic_vector_buffer</tt> to achieve
this goal (subsequent discussions will refer only to <tt>dynamic_string_buffer</tt>,
but also apply to <tt>dynamic_vector_buffer</tt>). The TS provides a family of
algorithms which allow the code above to be expressed more succinctly by
passing the dynamic buffer instead of the buffer sequence representing the
writable bytes. To facilitiate construction and lifetime management of these
dynamic buffers, the TS function <tt>dynamic_buffer</tt> is overloaded for
various container types to return a dynamic buffer instance, as seen in this
synchronous example:
</p>
<blockquote><pre>
string s;
auto const bytes_transferred = read(socket, dynamic_buffer(s));
assert(s.size() == bytes_transferred);
</pre></blockquote>
<p>
Given the semantics of dynamic buffers implied by the wording, instances of
dynamic buffers behave more like references to storage types rather than
storage types, as copies refer to the same underlying storage. This can be
seen in the declaration of <tt>dynamic_string_buffer</tt> which meets
the requirements of <b>DynamicBuffer</b>:
</p>
<blockquote><pre>
template &lt;typename Elem, typename Traits, typename Allocator&gt;
class dynamic_string_buffer
{
  [&hellip;]
private:
  std::basic_string&lt;Elem, Traits, Allocator&gt;&amp; string_;
  std::size_t size_;
  const std::size_t max_size_;
};
</pre></blockquote>
<p>
A dynamic string buffer contains a reference to the underlying string. Copies
of a dynamic string buffer refer to the same string. Note that the dynamic
string buffer also contains some state: the <tt>size_</tt> and <tt>max_size_</tt>
data members. This additional metadata informs the dynamic string buffer of
the boundaries between the readable and writable bytes, as well as the maximum
allowed size of the total of the readable and writable bytes.
<p/>

<a name="Async"></a><h3>Asynchronous Operations</h3>
<p>
Thus far we have shown synchronous (blocking) examples. Asynchronous algorithms
are started by a call to an <em>initiating function</em> which returns immediately.
When the operation has completed, the implementation invokes a caller-provided
function object called a <em>completion handler</em> with the result of the
operation, usually indicated by an error code and possible additional
data such as the number of bytes transacted. Here is a typical signature of an
asynchronous TS algorithm which accepts a dynamic buffer instance:
</p>
<blockquote><pre>
<i>// 17.10 [networking.ts::buffer.async.read.until], asynchronous delimited read operations:</i>

template&lt;
  class AsyncReadStream,
  class DynamicBuffer,
  class CompletionToken&gt;
<i>DEDUCED</i> async_read_until(
  AsyncReadStream&amp; s,
  DynamicBuffer&amp;&amp; b,
  char delim,
  CompletionToken&amp;&amp; token);
</pre></blockquote>
<p>
Since the initiating function returns immediately, it is necessary for the
asynchronous operation to manage the lifetime of the dynamic buffer parameter.
Guidance for doing so is given in the TS:
</p>
<blockquote>
<b>13.2.7.5 Lifetime of initiating function arguments [async.reqmts.async.lifetime]</b>
<p>
1. Unless otherwise specified, the lifetime of arguments to initiating functions shall be treated as 
follows: [&hellip;] the implementation does not assume the validity of the argument after the initiating 
function completes [&hellip;] The implementation may make copies of the argument, and all copies shall be
destroyed no later than immediately after invocation of the completion handler.
</p>
</blockquote>
<p>
Given that the requirement for dynamic buffers is that they are <b>MoveConstructible</b>,
a sensible implementation will make a decay-copy of the argument. An implementation
authored by the principal architect of the TS, does precisely that:
</p>
<blockquote><pre>
namespace detail {

template &lt;
    typename AsyncReadStream,
    typename DynamicBuffer,
    typename ReadHandler&gt;
class read_until_delim_op
{
public:
    // Note: DeducedDynamicBuffer will be DynamicBuffer plus cv-ref qualifiers
    template &lt;typename DeducedDynamicBuffer&gt;
    read_until_delim_op(
        AsyncReadStream&amp; stream,
        DeducedDynamicBuffer&amp;&amp; buffers,
        char delim, ReadHandler&amp; handler)
    : [&hellip;]
      buffers_(std::forward&lt;DeducedDynamicBuffer&gt;(buffers))
      [&hellip;]
    {
    }
    [&hellip;]
    void operator()(const std::error_code& ec,
                    std::size_t bytes_transferred, int start = 0);
    [&hellip;]
    DynamicBuffer buffers_;
    [&hellip;]
};

} // detail

template &lt;
    typename AsyncReadStream,
    typename DynamicBuffer,
    typename ReadHandler&gt;
NET_TS_INITFN_RESULT_TYPE(ReadHandler,
    void (std::error_code, std::size_t))
async_read_until(
    AsyncReadStream&amp; s,
    DynamicBuffer&amp;&amp; buffers,
    char delim,
    ReadHandler&amp;&amp; handler)
{
  // If you get an error on the following line it means that your handler does
  // not meet the documented type requirements for a ReadHandler.
  NET_TS_READ_HANDLER_CHECK(ReadHandler, handler) type_check;

  async_completion&lt;ReadHandler,
    void (std::error_code, std::size_t)&gt; init(handler);

  detail::read_until_delim_op&lt;
    AsyncReadStream,
    typename std::decay&lt;DynamicBuffer&gt;::type,
    NET_TS_HANDLER_TYPE(ReadHandler, void (std::error_code, std::size_t))&gt;(
        s,
        std::forward&lt;DynamicBuffer&gt;(buffers),
        delim,
        init.completion_handler)(std::error_code(), 0, 1);

  return init.result.get();
}
</pre></blockquote>
<p>
Further evidence that a dynamic buffer represents a lightweight non-owning
reference to storage is provided by this quote from LWG issue by the same
author as the TS:
</p>
<blockquote>
Asio's implementation (and the intended specification) performs <tt>DECAY_COPY(b)</tt>
in the <tt>async_read</tt>, <tt>async_write</tt>, and <tt>async_read_until</tt>
initiating functions. All operations performed on b are actually performed on
that decay-copy, or on a move-constructed descendant of it. <b>The copy is intended 
to refer to the same underlying storage and be otherwise interchangeable with
the original in every way</b>. <em>[Ed: emphasis added]</em>
</blockquote>

<p>
When asynchronous algorithms are constructed from calls to other initiating functions,
the result is called a <em>composed operation</em>. For example, <tt>async_read</tt> may be 
implemented in terms of zero or more calls to a stream's <tt>async_read_some</tt> algorithm. 
Layers of composition may be added to arbitrary degree, permitting algorithms of
significant complexity to be developed. A composed operation is typically implemented
as a movable function object containing additional state. When the composed
operation calls an intermediate initiating function, it passes <tt>std::move(*this)</tt>
as the completion handler. This idiom, informally known as "stack-ripping" is used for
reasons related to the optimization of memory allocation during asynchronous continuations
(and beyond the scope of this paper). Here is the function call operator for the
<tt>async_read_until</tt> operation described above:
</p>
<blockquote><pre>
void read_until_op::operator()(const std::error_code& ec,
                               std::size_t bytes_transferred, int start = 0)
{
    ...
    
    // Start a new asynchronous read operation to obtain more data.
    stream_.async_read_some(buffers_.prepare(bytes_to_read), std::move(*this));

    ...
}
</pre></blockquote>
<p>
Care must be taken when using this idiom, as the memory location of data
members belonging to the function object will necessarily change every time
an initiating function is invoked. The implication is that lvalue references
belonging to any of the data members of the function object may not
be passed to intermediate initiating functions. This prevents internal
interfaces where dynamic buffers are passed by reference.
</p>



<a name="Composition"></a><h3>Problem with Composition</h3>
<p>
When only one composed operation handles the dynamic buffer, things seem to
work. However, if a composed operation wishes to invoke another composed
operation on that dynamic buffer, a problem arises. Consider a composed
operation <tt>async_http_read</tt> which accepts a dynamic buffer, and is
implemented in terms of zero or more calls to <tt>async_http_read_some</tt>,
which also accepts the same dynamic buffer. When <tt>async_http_read_some</tt>
is invoked, it takes ownership of the buffer via move. However, if
<tt>async_http_read</tt> needs to invoke <tt>async_http_read_some</tt>
again to fufill its contract, it cannot do so without depending on undefined
behavior. This is because on the second and subsequent invocations of
<tt>async_http_read_some</tt>, the buffer is in a moved-from state, and
the specification of <b>DynamicBuffer</b> is silent on the condition of
moved-from objects. This can be seen in the implementation of the functions
described above, which come from the Boost.Beast [2] library:
</p>
<blockquote><pre>
template&lt;class Stream, class DynamicBuffer,
    bool isRequest, class Derived, class Condition,
        class Handler&gt;
class read_http_op : public boost::asio::coroutine
{
    Stream& stream_;
    DynamicBuffer buffer_;
    basic_parser<isRequest, Derived>& parser_;
    std::size_t bytes_transferred_ = 0;
    Handler handler_;
    bool cont_ = false;

public:
    [&hellip;]

    void
    operator()(
        error_code ec,
        std::size_t bytes_transferred = 0,
        bool cont = true)
    {
        cont_ = cont;
        BOOST_ASIO_CORO_REENTER(*this)
        {
            if(Condition{}(p_))
            {
                BOOST_ASIO_CORO_YIELD
                boost::asio::post(stream_.get_executor(),
                    bind_handler(std::move(*this), ec));
                goto upcall;
            }
            for(;;)
            {
                // The call to async_http_read_some will produce
                // undefined behavior on the second and subsequent
                // calls, since buffer_ is in the moved-from state:
                //
                BOOST_ASIO_CORO_YIELD
                async_http_read_some(
                    stream_, std::move(buffer_), parser_, std::move(*this));

                if(ec)
                    goto upcall;
                bytes_transferred_ += bytes_transferred;
                if(Condition{}(p_))
                    goto upcall;
            }
        upcall:
            handler_(ec, bytes_transferred_);
        }
    }
};
</pre></blockquote>



<a name="Exception"></a><h3>Problem with Exceptions</h3>
<p>
Another design problem caused by adding metadata to the dynamic buffer concept
is illustrated in the following example code:
</p>
<blockquote><pre>
template&lt;class MutableBufferSequence&gt;
std::size_t read(const MutableBufferSequence&amp;)
{
  throw std::exception{};
}

int main()
{
  std::string s;
  assert(s.empty());
  try
  {
    auto b = boost::asio::dynamic_buffer(s);
    b.commit(read(b.prepare(32)));
  }
  catch(const std::exception&amp;)
  {
    assert(s.empty()); // fails
  }
}
</pre></blockquote>
<p>
While not technically incorrect, it may be surprising to the user that
the string contains additional value-initialized data which was not part
of the original readable bytes (which in this case was empty). The wording of
<b>DynamicBuffer</b> and <tt>dynamic_string_buffer</tt> do not address
the effect of exceptions on state of the readable bytes in the underlying
container at all.
</p>

<a name="Workarounds"></a><h3>Problem with Workarounds</h3>
<p>
The problem of composition can be solved using the current specification of
<b>DynamicBuffer</b> by creating a copyable, reference-counted wrapper which
holds a decay-copy of the dynamic buffer argument. Here is a partial sketch
of that wrapper:
</p>
<blockquote><pre>
template&lt;class DynamicBuffer&gt;
class shared_buffer
{
    std::shared_ptr&lt;DynamicBuffer&gt; p_;

public:
    template&lt;class DeducedDynamicBuffer&gt;
    explicit shared_buffer(DeducedDynamicBuffer&amp;&amp; b)
        : p_(std::make_shared&lt;DynamicBuffer&gt;(std::forward&lt;DeducedDynamicBuffer&gt;(b)))
    {
    }

    [&hellip;]

    mutable_buffers_type prepare(std::size_t n);
    void commit(std::size_t);
};
</pre></blockquote>
<p>
An existing initiating function may be adjusted to use the wrapper instead,
as shown:
</p>
<blockquote><pre>
template &lt;
    typename AsyncReadStream,
    typename DynamicBuffer,
    typename ReadHandler&gt;
NET_TS_INITFN_RESULT_TYPE(ReadHandler,
    void (std::error_code, std::size_t))
async_read_until(
    AsyncReadStream&amp; s,
    DynamicBuffer&amp;&amp; buffers,
    char delim,
    ReadHandler&amp;&amp; handler)
{
  // If you get an error on the following line it means that your handler does
  // not meet the documented type requirements for a ReadHandler.
  NET_TS_READ_HANDLER_CHECK(ReadHandler, handler) type_check;

  async_completion&lt;ReadHandler,
    void (std::error_code, std::size_t)&gt; init(handler);

  read_until_delim_op&lt;
    AsyncReadStream,
    shared_buffer&lt;typename std::decay&lt;DynamicBuffer&gt;::type&gt;,
    NET_TS_HANDLER_TYPE(ReadHandler, void (std::error_code, std::size_t))&gt;(
        s,
        shared_buffer&lt;typename std::decay&lt;DynamicBuffer&gt;::type&gt{std::forward&lt;DynamicBuffer&gt;(buffers)},
        delim,
        init.completion_handler)(std::error_code(), 0, 1);

  return init.result.get();
}
</pre></blockquote>
<p>
This workaround comes at the expense of one or more additional unnecessary
dynamic allocations. Utilizing the workaround would sacrifice an important
C++ value: zero-cost abstractions.
</p>

</p>
<a name="Solution"></a><h2>Solution</h2>
<p>
The Net.TS predecessors Asio and Boost.Asio offered only the single
concrete type <tt>basic_streambuf</tt> which addressed the problem of
stream algorithms which need to write a dynamic number of octets. The
interface to <tt>basic_streambuf</tt> almost perfectly matches the
requirements of <b>DynamicBuffer</b>.
Field experience showed that users had trouble manipulating buffer sequences,
often writing code which performed unnecessary allocate/copy/free cycles
to get the data in the format they desired. Users loudly declared the
desire to send and receive their data using traditional containers such
as <tt>string</tt> and <tt>vector</tt> with which they are already very
familiar.
</p>
<p>
To support the use of strings and vectors in stream algorithms, the
<b>DynamicBuffer</b> concept is fashioned as a non-owning reference wrapper
to a supported container type whose lifetime is managed by the caller. Algorithms
which accept dynamic buffer parameters use a forwarding reference, and take
ownership via decay-copy. Net.TS provides some functions which accept a
reference to a user-managed container and return a suitable wrapper. These
functions are designed for ease of use, as they may be invoked directly at
call sites:
</p>
<blockquote><pre>
<i>// 16.14 [networking.ts::buffer.dynamic.creation], dynamic buffer creation:</i>

template&lt;class T, class Allocator&gt;
  dynamic_vector_buffer&lt;T, Allocator&gt;
  dynamic_buffer(vector&lt;T, Allocator&gt;&amp; vec) noexcept;
template&lt;class T, class Allocator&gt;
  dynamic_vector_buffer&lt;T, Allocator&gt;
  dynamic_buffer(vector&lt;T, Allocator&gt;&amp; vec, size_t n) noexcept;

template&lt;class CharT, class Traits, class Allocator&gt;
  dynamic_string_buffer&lt;CharT, Traits, Allocator&gt;
  dynamic_buffer(basic_string&lt;CharT, Traits, Allocator&gt;&amp; str) noexcept;
template&lt;class CharT, class Traits, class Allocator&gt;
  dynamic_string_buffer&lt;CharT, Traits, Allocator&gt;
  dynamic_buffer(basic_string&lt;CharT, Traits, Allocator&gt;&amp; str, size_t n) noexcept;
</pre></blockquote>
<p>
The solution we propose is to change the semantics of <b>DynamicBuffer</b>
to represent a true storage type rather than a hybrid reference with metadata.
Instances of dynamic buffers will be passed by reference, and callers will
be required to manage the lifetime of dynamic buffer objects for the duration
of any asynchronous operations. As the caller is already responsible for
maintaining the lifetime of the stream, and the <tt>string</tt> or <tt>vector</tt>
under the old scheme, maintaining the lifetime of the dynamic buffer in the
new scheme poses no additional burden. To solve the problem of exceptions,
our solution refactors the dynamic buffer wrappers to own their corresponding
string and vector. The caller can only take ownership of the container by
calling a member function. The implementation then has the opportunity to
adjust the container to reflect the correct state before the caller receives
the value.
</p>
<p>
We believe that there is a tension between ease of use and utility. The current
specification provides ease of use but lacks utility, specifically the ability
to compose higher level operations out of lower level ones which operate on
dynamic buffers. The solution we propose goes in the opposite direction,
sacrificing some ease of use in exchange for the ability to compose. The
author of this paper favor composition over ease of use for a few reasons.
First, because C++ should always prioritize zero-cost abstractions as
a primary factor to differentiate itself from other languages. And second,
because the entirety of Net.TS provides <em>low-level abstractions</em>. Most
users should not be concerning themselves with interacting at the raw TCP/IP
level. Instead they should be working with high level interfaces such as
HTTP client APIs, WebSocket framework APIs, REST SDKs, and the like. These
high level interfaces do not exist yet, but with the standardization of low
level networking provided in Net.TS we are confident they will arrive in
due time.
</p>
<p>
Note that writing asynchronous code in a clean style which uses all the
modern idioms of Asio and Net.TS is quite a difficult endeavor, as evidenced
by the distinct lack of open source libraries built on Asio which follow
best practices (despite Asio having been available for over a decade).
Furthermore the number of people who need to author components which interact
directly with low-level interfaes such as Asio or Net.TS should be few in number.
Therefore, we favor a Net.TS specification which addresses the needs for
writers of specialized middleware and high-level interfaces over ordinary
users.
</p>
<p>
The author of this paper is not entirely satisfied with the proposed solution,
and believes that finding the right solution which balances the needs of utility
with ease of use is probably best left to the LEWG.
</p>

<a name="Wording"></a><h2>Proposed Wording</h2>
<p>This wording is relative to <a href="http://wg21.link/n4734">N4734</a>.</p>

<blockquote class="note">
<p>[<i>Drafting note:</i> The project editor is kindly asked to replace all occurrences of 
<tt>DynamicBuffer&amp;&amp;</tt> with <tt>DynamicBuffer&amp;</tt> as indicated by the provided
wording changes below. &mdash; <i>end drafting note</i>]
</p>
</blockquote>

<ol>
<li><p>Modify 16.1 [networking.ts::buffer.synop], header <tt>&lt;experimental/buffer&gt;</tt> synopsis, 
as indicated:</p>
<blockquote><pre>
[&hellip;]
<i>// 16.11 [networking.ts::buffer.creation], buffer creation:</i>
[&hellip;]

template&lt;class T, class Allocator<ins> = allocator&lt;T&gt;</ins>&gt;
class dynamic_vector_buffer;

template&lt;class CharT, class Traits<ins> = char_traits&lt;CharT&gt;</ins>, class Allocator<ins> = allocator&lt;CharT&gt;</ins>&gt;
class <ins>basic_</ins>dynamic_string_buffer;

<ins>using dynamic_string_buffer = basic_dynamic_string_buffer&lt;char&gt;;</ins>

<del><i>// 16.14 [networking.ts::buffer.dynamic.creation], dynamic buffer creation:</i></del>

<del>template&lt;class T, class Allocator&gt;
  dynamic_vector_buffer&lt;T, Allocator&gt;
  dynamic_buffer(vector&lt;T, Allocator&gt;&amp; vec) noexcept;
template&lt;class T, class Allocator&gt;
  dynamic_vector_buffer&lt;T, Allocator&gt;
  dynamic_buffer(vector&lt;T, Allocator&gt;&amp; vec, size_t n) noexcept;
</del>

<del>template&lt;class CharT, class Traits, class Allocator&gt;
  dynamic_string_buffer&lt;CharT, Traits, Allocator&gt;
  dynamic_buffer(basic_string&lt;CharT, Traits, Allocator&gt;&amp; str) noexcept;
template&lt;class CharT, class Traits, class Allocator&gt;
  dynamic_string_buffer&lt;CharT, Traits, Allocator&gt;
  dynamic_buffer(basic_string&lt;CharT, Traits, Allocator&gt;&amp; str, size_t n) noexcept;</del>

[&hellip;]
<i>// 17.5 [networking.ts::buffer.read], synchronous read operations:</i>
[&hellip;]

template&lt;class SyncReadStream, class DynamicBuffer&gt;
  size_t read(SyncReadStream&amp; stream, DynamicBuffer&amp;<del>&amp;</del> b);
template&lt;class SyncReadStream, class DynamicBuffer&gt;
  size_t read(SyncReadStream&amp; stream, DynamicBuffer&amp;<del>&amp;</del> b, error_code&amp; ec);
template&lt;class SyncReadStream, class DynamicBuffer, class CompletionCondition&gt;
  size_t read(SyncReadStream&amp; stream, DynamicBuffer&amp;<del>&amp;</del> b,
              CompletionCondition completion_condition);
template&lt;class SyncReadStream, class DynamicBuffer, class CompletionCondition&gt;
  size_t read(SyncReadStream&amp; stream, DynamicBuffer&amp;<del>&amp;</del> b,
              CompletionCondition completion_condition, error_code&amp; ec);

<i>// 17.6 [networking.ts::buffer.async.read], asynchronous read operations:</i>              
[&hellip;]

template&lt;class AsyncReadStream, class DynamicBuffer, class CompletionToken&gt;
  <i>DEDUCED</i> async_read(AsyncReadStream&amp; stream,
                     DynamicBuffer&amp;<del>&amp;</del> b, CompletionToken&amp;&amp; token);
template&lt;class AsyncReadStream, class DynamicBuffer,
  class CompletionCondition, class CompletionToken&gt;
    <i>DEDUCED</i> async_read(AsyncReadStream&amp; stream,
                       DynamicBuffer&amp;<del>&amp;</del> b,
                       CompletionCondition completion_condition,
                       CompletionToken&amp;&amp; token);

<i>// 17.7 [networking.ts::buffer.write], synchronous write operations:</i>                       
[&hellip;]

template&lt;class SyncWriteStream, class DynamicBuffer&gt;
  size_t write(SyncWriteStream&amp; stream, DynamicBuffer&amp;<del>&amp;</del>; b);
template&lt;class SyncWriteStream, class DynamicBuffer&gt;
  size_t write(SyncWriteStream&amp; stream, DynamicBuffer&amp;<del>&amp;</del> b, error_code&amp; ec);
template&lt;class SyncWriteStream, class DynamicBuffer, class CompletionCondition&gt;
  size_t write(SyncWriteStream&amp; stream, DynamicBuffer&amp;<del>&amp;</del> b,
               CompletionCondition completion_condition);
template&lt;class SyncWriteStream, class DynamicBuffer, class CompletionCondition&gt;
  size_t write(SyncWriteStream&amp; stream, DynamicBuffer&amp;<del>&amp;</del> b,
               CompletionCondition completion_condition, error_code&amp; ec);

<i>// 17.8 [networking.ts::buffer.async.write], asynchronous write operations:</i>               
[&hellip;]

template&lt;class AsyncWriteStream, class DynamicBuffer, class CompletionToken&gt;
  <i>DEDUCED</i> async_write(AsyncWriteStream&amp; stream,
                      DynamicBuffer&amp;<del>&amp;</del> b, CompletionToken&amp;&amp; token);
template&lt;class AsyncWriteStream, class DynamicBuffer,
  class CompletionCondition, class CompletionToken&gt;
    <i>DEDUCED</i> async_write(AsyncWriteStream&amp; stream,
                        DynamicBuffer&amp;<del>&amp;</del> b,
                        CompletionCondition completion_condition,
                        CompletionToken&amp;&amp; token);

<i>// 17.9 [networking.ts::buffer.read.until], synchronous delimited read operations:</i>                        

template&lt;class SyncReadStream, class DynamicBuffer&gt;
  size_t read_until(SyncReadStream&amp; s, DynamicBuffer&amp;<del>&amp;</del> b, char delim);
template&lt;class SyncReadStream, class DynamicBuffer&gt;
  size_t read_until(SyncReadStream&amp; s, DynamicBuffer&amp;<del>&amp;</del> b,
                    char delim, error_code&amp; ec);
template&lt;class SyncReadStream, class DynamicBuffer&gt;
  size_t read_until(SyncReadStream&amp; s, DynamicBuffer&amp;<del>&amp;</del> b, string_view delim);
template&lt;class SyncReadStream, class DynamicBuffer&gt;
  size_t read_until(SyncReadStream&amp; s, DynamicBuffer&amp;<del>&amp;</del> b,
                    string_view delim, error_code&amp; ec);

<i>// 17.10 [networking.ts::buffer.async.read.until], asynchronous delimited read operations:</i>

template&lt;class AsyncReadStream, class DynamicBuffer, class CompletionToken&gt;
  <i>DEDUCED</i> async_read_until(AsyncReadStream&amp; s,
                           DynamicBuffer&amp;<del>&amp;</del> b, char delim,
                           CompletionToken&amp;&amp; token);
template&lt;class AsyncReadStream, class DynamicBuffer, class CompletionToken&gt;
  <i>DEDUCED</i> async_read_until(AsyncReadStream&amp; s,
                           DynamicBuffer&amp;<del>&amp;</del> b, string_view delim,
                           CompletionToken&amp;&amp; token);

[&hellip;]
</pre></blockquote>
</li>

<li>
<p>Modify 16.2.4 [networking.ts::buffer.reqmts.dynamicbuffer], as indicated:</p>

<blockquote>
<p>
-1- [&hellip;]
<p/>
-2- A type <tt>X</tt> meets the <tt>DynamicBuffer</tt> requirements if it satisfies
the requirements of <tt>Destructible</tt> (C++ 2014 [destructible])
<del>and <tt>MoveConstructible</tt> (C++ 2014 [moveconstructible]),</del>
as well as the additional requirements listed in Table 14.
</p>
</blockquote>
</li>

<li><p>Modify 16.12 [networking.ts::buffer.dynamic.vector], as indicated:</p>

<blockquote>
<blockquote><pre>
[&hellip;]

template&lt;class T, class Allocator<ins> = allocator&lt;T&gt;</ins>&gt;
class dynamic_vector_buffer
{
public:
  <em>// types:</em>
  <ins>using value_type = vector&lt;T, Allocator&gt;;</ins>
  using const_buffers_type = const_buffer;
  using mutable_buffers_type = mutable_buffer;
  
  <em>// constructors:</em>
  <ins>dynamic_vector_buffer();</ins>
  <ins>explicit dynamic_vector_buffer(size_t maximum_size);</ins>
  explicit dynamic_vector_buffer(<del>vector&lt;T, Allocator&gt;</del><ins>value_type</ins>&amp;<ins>&amp;</ins> vec) noexcept;
  dynamic_vector_buffer(<del>vector&lt;T, Allocator&gt;</del><ins>value_type</ins>&amp;<ins>&amp;</ins> vec, size_t maximum_size)<del> noexcept</del>;
  dynamic_vector_buffer(dynamic_vector_buffer&amp;&amp;<ins> other</ins>)<del> = default</del><ins> noexcept</ins>;
  
  <ins>dynamic_vector_buffer&amp; operator=(const dynamic_vector_buffer&amp;) = delete;</ins>
  
  <em>// members:</em>
  size_t size() const noexcept;
  size_t max_size() const noexcept;
  <ins>void max_size(size_t maximum_size) noexcept;</ins>
  size_t capacity() const noexcept;
  const_buffers_type data() const noexcept;
  mutable_buffers_type prepare(size_t n);
  void commit(size_t n);
  void consume(size_t n);
  <ins>span&lt;const T&gt; get() const noexcept;</ins>
  <ins>value_type release();</ins>

private:
  <del>vector&lt;T, Allocator&gt;&amp;</del> <ins>value_type</ins> vec_; <em>// exposition only</em>
  size_t size_; <em>// exposition only</em>
  <del>const</del> size_t max_size_; <em>// exposition only</em>
};

[&hellip;]
</pre></blockquote>
<p>
-2- [&hellip;]
<p/>
-3- [&hellip;]
</p>
<pre>
<ins>dynamic_vector_buffer();</ins>
</pre>
<blockquote>
<p>
<ins>-?- <em>Effects:</em> Default-constructs <tt>vec_</tt>. Initializes <tt>size_</tt> with <tt>0</tt>, and 
<tt>max_size_</tt> with <tt>vec_.max_size()</tt>.</ins>
</p>
</blockquote>
<pre>
<ins>explicit dynamic_vector_buffer(size_t maximum_size);</ins>
</pre>
<blockquote>
<p>
<ins>-?- <em>Effects:</em> Default-constructs <tt>vec_</tt>. Initializes <tt>size_</tt> with <tt>0</tt>, and 
<tt>max_size_</tt> with <tt>maximum_size</tt>.</ins>
</p>
</blockquote>
<pre>
explicit dynamic_vector_buffer(<del>vector&lt;T, Allocator&gt;</del><ins>value_type</ins>&amp;<ins>&amp;</ins> vec) noexcept;
</pre>
<blockquote>
<p>
-4- <em>Effects:</em> Initializes <tt>vec_</tt> with <tt><ins>std::move(</ins>vec<ins>)</ins></tt>,
  <tt>size_</tt> with <tt>vec<ins>_</ins>.size()</tt>, and <tt>max_size_</tt> with <tt>vec<ins>_</ins>.max_size()</tt>.
</p>
</blockquote>
<pre>
dynamic_vector_buffer(<del>vector&lt;T, Allocator&gt;</del><ins>value_type</ins>&amp;<ins>&amp;</ins> vec,
                      size_t maximum_size) <del>noexcept</del>;
</pre>
<blockquote>
<p>
-5- <em>Requires:</em> <tt>vec.size() &lt;= maximum_size</tt>.
<p/>
-6- <em>Effects:</em> Initializes <tt>vec_</tt> with <tt><ins>std::move(</ins>vec<ins>)</ins></tt>,
  <tt>size_</tt> with <tt>vec<ins>_</ins>.size()</tt>, and <tt>max_size_</tt> with <tt>maximum_size</tt>.
</p>
</blockquote>
<pre>
<ins>dynamic_vector_buffer(dynamic_vector_buffer&amp;&amp; other) noexcept;</ins>
</pre>
<blockquote>
<p>
<ins>-?- <em>Effects:</em> Initializes <tt>vec_</tt> with <tt>std::move(other.vec_)</tt>,
  <tt>size_</tt> with <tt>other.size_</tt>, and <tt>max_size_</tt> with <tt>other.max_size_</tt>. Then calls
  <tt>other.vec_.clear();</tt> and assigns 0 to <tt>other.size_</tt>.</ins>
<p/>
[&hellip;]
</p>
</blockquote>
<pre>
size_t max_size() const noexcept;
</pre>
<blockquote>
<p>
-8- <i>Returns:</i> <tt>max_size_</tt>.
</p>
</blockquote>
<pre>
<ins>void max_size(size_t maximum_size);</ins>
</pre>
<blockquote>
<p>
<ins>-?- <em>Requires:</em> <tt>size() &lt;= maximum_size</tt>.</ins> 
<p/>
<ins>-?- <em>Effects:</em> Performs <tt>max_size_ = maximum_size</tt>.</ins>
</p>
</blockquote>
[&hellip;]
<pre>
void consume(size_t n);
</pre>
<blockquote>
<p>
-15- <i>Effects:</i> [&hellip;]
</p>
</blockquote>
<pre>
<ins>span&lt;const T&gt; get() const noexcept;</ins>
</pre>
<blockquote>
<p>
<ins>-?- <em>Returns:</em> <tt>span&lt;const T&gt;(vec_.data(), size_)</tt>.</ins>
</p>
</blockquote>
<pre>
<ins>value_type release();</ins>
</pre>
<blockquote>
<p>
<ins>-?- <em>Effects:</em> Equivalent to:</ins>
<blockquote>
<pre><ins>
vec_.resize(size_);
value_type tmp = std::move(vec_);
vec_.clear();
size_ = 0;
return tmp;
</ins></pre>
</blockquote>
</p>
</blockquote>
</blockquote>
</li>

<li><p>Modify 16.13 [networking.ts::buffer.dynamic.string], as indicated:</p>

<blockquote>
<blockquote><pre>
template&lt;class CharT, class Traits<ins> = char_traits&lt;CharT&gt;</ins>, class Allocator<ins> = allocator&lt;CharT&gt;</ins>&gt;
class <ins>basic_</ins>dynamic_string_buffer
{
public:
  <em>// types:</em>
  <ins>using value_type = basic_string&lt;CharT, Traits, Allocator&gt;;</ins>
  using const_buffers_type = const_buffer;
  using mutable_buffers_type = mutable_buffer;

  <em>// constructors:</em>
  <ins>basic_dynamic_string_buffer();</ins>
  <ins>explicit basic_dynamic_string_buffer(size_t maximum_size);</ins>
  explicit <ins>basic_</ins>dynamic_string_buffer(<del>basic_string&lt;CharT, Traits, Allocator&gt;</del><ins>value_type</ins>&amp;<ins>&amp;</ins> str) noexcept;
  <ins>basic_</ins>dynamic_string_buffer(<del>basic_string&lt;CharT, Traits, Allocator&gt;</del><ins>value_type</ins>&amp;<ins>&amp;</ins> str, size_t maximum_size) <del>noexcept</del>;
  <ins>basic_</ins>dynamic_string_buffer(<ins>basic_</ins>dynamic_string_buffer&amp;&amp;<ins> other</ins>)<del> = default</del><ins> noexcept</ins>;

  <ins>basic_dynamic_string_buffer&amp; operator=(basic_dynamic_string_buffer&amp;&amp;) = delete;</ins>
  
  <em>// members:</em>
  size_t size() const noexcept;
  size_t max_size() const noexcept;
  <ins>void max_size(size_t maximum_size) noexcept;</ins>
  size_t capacity() const noexcept;
  const_buffers_type data() const noexcept;
  mutable_buffers_type prepare(size_t n);
  void commit(size_t n) noexcept;
  void consume(size_t n);
  <ins>basic_string_view&lt;CharT, Traits&gt; get() const noexcept;</ins>
  <ins>value_type release();</ins>

private:
  <del>basic_string&lt;CharT, Traits, Allocator&gt;&amp;</del> <ins>value_type</ins> str_; <em>// exposition only</em>
  size_t size_; <em>// exposition only</em>
  <del>const </del>size_t max_size_; <em>// exposition only</em>
};
</pre>
</blockquote>
<p>
-2- [&hellip;]
<p/>
-3- [&hellip;]
</p>
<pre>
<ins>basic_dynamic_string_buffer();</ins>
</pre>
<blockquote>
<p>
<ins>-?- <em>Effects:</em> Default-constructs <tt>str_</tt>. Initializes <tt>size_</tt> with <tt>0</tt>, and 
<tt>max_size_</tt> with <tt>str_.max_size()</tt>.</ins>
</p>
</blockquote>
<pre>
<ins>explicit basic_dynamic_string_buffer(size_t maximum_size);</ins>
</pre>
<blockquote>
<p>
<ins>-?- <em>Effects:</em> Default-constructs <tt>str_</tt>. Initializes <tt>size_</tt> with <tt>0</tt>, and 
<tt>max_size_</tt> with <tt>maximum_size</tt>.</ins>
</p>
<p>[&hellip;]</p>
</blockquote>
<pre>
explicit <ins>basic_</ins>dynamic_string_buffer(<del>basic_string&lt;CharT, Traits, Allocator&gt;</del><ins>value_type</ins>&amp;<ins>&amp;</ins> str) noexcept;
</pre>
<blockquote>
<p>
-4- <em>Effects:</em> Initializes <tt>str_</tt> with <tt><ins>std::move(</ins>str<ins>)</ins></tt>,
  <tt>size_</tt> with <tt>str<ins>_</ins>.size()</tt>, and <tt>max_size_</tt> with <tt>str<ins>_</ins>.max_size()</tt>
</p>
</blockquote>
<pre>
<ins>basic_</ins>dynamic_string_buffer(<del>basic_string&lt;CharT, Traits, Allocator&gt;</del><ins>value_type</ins>&amp;<ins>&amp;</ins> str,
                            size_t maximum_size)<del> noexcept</del>;
</pre>
<blockquote>
<p>
-5- <em>Requires:</em> <tt>str.size() &lt;= maximum_size</tt>.
</p>
<p>
-6- <em>Effects:</em> Initializes <tt>str_</tt> with <tt><ins>std::move(</ins>str<ins>)</ins></tt>,
  <tt>size_</tt> with <tt>str<ins>_</ins>.size()</tt>, and <tt>max_size_</tt> with <tt>maximum_size</tt>.
</p>
</blockquote>
<pre>
<ins>basic_dynamic_string_buffer(basic_dynamic_string_buffer&amp;&amp; other) noexcept;</ins>
</pre>
<blockquote>
<p>
<ins>-?- <em>Effects:</em> Initializes <tt>str_</tt> with <tt>std::move(other.str_)</tt>,
  <tt>size_</tt> with <tt>other.size_</tt>, and <tt>max_size_</tt> with <tt>other.max_size_</tt>. Then calls
  <tt>other.str_.clear();</tt> and assigns 0 to <tt>other.size_</tt>.</ins>
</p>
<p>[&hellip;]</p>
</blockquote>
<pre>
size_t max_size() const noexcept;
</pre>
<blockquote>
<p>
-8- <em>Returns:</em> <tt>max_size_</tt>.
</p>
</blockquote>
<pre>
<ins>void max_size(size_t maximum_size);</ins>
</pre>
<blockquote>
<p>
<ins>-?- <em>Requires:</em> <tt>size() &lt;= maximum_size</tt>.</ins> 
<p/>
<ins>-?- <em>Effects:</em> Performs <tt>max_size_ = maximum_size</tt>.</ins>
</p>
</blockquote>
<p>[&hellip;]</p>
<pre>
void consume(size_t n);
</pre>
<blockquote>
<p>
-15- <i>Effects:</i> [&hellip;]
</p>
</blockquote>
<pre>
<ins>basic_string_view&lt;CharT, Traits&gt; get() const noexcept;</ins>
</pre>
<blockquote>
<p>
<ins>-?- <em>Returns:</em> <tt>basic_string_view&lt;CharT, Traits&gt;(str_.data(), size_)</tt>.</ins>
</p>
</blockquote>
<pre>
<ins>value_type release();</ins>
</pre>
<blockquote>
<p>
<ins>-?- <em>Effects:</em> Equivalent to:</ins>
<blockquote>
<pre><ins>
str_.resize(size_);
value_type tmp = std::move(str_);
str_.clear();
size_ = 0;
return tmp;
</ins></pre>
</blockquote>
</p>
</blockquote>
</blockquote>
</li>

<li><p>Remove 16.14 [networking.ts::buffer.dynamic.creation] entirely.</p></li>

<li><p>Modify 17.5 [networking.ts::buffer.read], as indicated:</p>
<blockquote>
<pre>
[&hellip;]

template&lt;class SyncReadStream, class DynamicBuffer&gt;
  size_t read(SyncReadStream&amp; stream, DynamicBuffer&amp;<del>&amp;</del> b);
template&lt;class SyncReadStream, class DynamicBuffer&gt;
  size_t read(SyncReadStream&amp; stream, DynamicBuffer&amp;<del>&amp;</del> b, error_code&amp; ec);
template&lt;class SyncReadStream, class DynamicBuffer,
  class CompletionCondition&gt;
    size_t read(SyncReadStream&amp; stream, DynamicBuffer&amp;<del>&amp;</del> b,
                CompletionCondition completion_condition);
template&lt;class SyncReadStream, class DynamicBuffer,
  class CompletionCondition&gt;
    size_t read(SyncReadStream&amp; stream, DynamicBuffer&amp;<del>&amp;</del> b,
                CompletionCondition completion_condition,
                error_code&amp; ec);

[&hellip;]
</pre>
</blockquote>
</li>

<li><p>Modify 17.6 [networking.ts::buffer.async.read], as indicated:</p>
<blockquote>
<pre>
[&hellip;]

template&lt;class AsyncReadStream, class DynamicBuffer, class CompletionToken&gt;
  <i>DEDUCED</i> async_read(AsyncReadStream&amp; stream,
                     DynamicBuffer&amp;<del>&amp;</del> b, CompletionToken&amp;&amp; token);
template&lt;class AsyncReadStream, class DynamicBuffer, class CompletionCondition,
  class CompletionToken&gt;
    <i>DEDUCED</i> async_read(AsyncReadStream&amp; stream,
                       DynamicBuffer&amp;<del>&amp;</del> b,
                       CompletionCondition completion_condition,
                       CompletionToken&amp;&amp; token);

[&hellip;]
</pre>
<p>
-14- The program shall ensure <ins>both</ins> the <tt>AsyncReadStream</tt>
object <tt>stream</tt> <ins>and the <tt>DynamicBuffer</tt> object <tt>b</tt>
are</ins><del> is</del> valid until the completion handler for the asynchronous
operation is invoked.
</p>
</blockquote>
</li>

<li><p>Modify 17.7 [networking.ts::buffer.write], as indicated:</p>
<blockquote><pre>
[&hellip;]

template&lt;class SyncWriteStream, class DynamicBuffer&gt;
  size_t write(SyncWriteStream&amp; stream, DynamicBuffer&amp;<del>&amp;</del> b);
template&lt;class SyncWriteStream, class DynamicBuffer&gt;
  size_t write(SyncWriteStream&amp; stream, DynamicBuffer&amp;<del>&amp;</del> b, error_code&amp; ec);
template&lt;class SyncWriteStream, class DynamicBuffer, class CompletionCondition&gt;
  size_t write(SyncWriteStream&amp; stream, DynamicBuffer&amp;<del>&amp;</del> b,
               CompletionCondition completion_condition);
template&lt;class SyncWriteStream, class DynamicBuffer, class CompletionCondition&gt;
  size_t write(SyncWriteStream&amp; stream, DynamicBuffer&amp;<del>&amp;</del> b,
               CompletionCondition completion_condition,
               error_code&amp; ec);

[&hellip;]
</pre></blockquote>
</li>

<li><p>Modify 17.8 [networking.ts::buffer.async.write], as indicated:</p>
<blockquote><pre>
[&hellip;]

template&lt;class AsyncWriteStream, class DynamicBuffer, class CompletionToken&gt;
  <i>DEDUCED</i> async_write(AsyncWriteStream&amp; stream,
                      DynamicBuffer&amp;<del>&amp;</del> b, CompletionToken&amp;&amp; token);
template&lt;class AsyncWriteStream, class DynamicBuffer, class CompletionCondition,
  class CompletionToken&gt;
    <i>DEDUCED</i> async_write(AsyncWriteStream&amp; stream,
                        DynamicBuffer&amp;<del>&amp;</del> b,
                        CompletionCondition completion_condition,
                        CompletionToken&amp;&amp; token);

[&hellip;]
</pre>
<p>
-14- The program shall ensure both the <tt>AsyncWriteStream</tt> object
<tt>stream</tt> and the <ins><tt>DynamicBuffer</tt> object <tt>b</tt></ins>
<del>memory associated with the dynamic buffer <tt>b</tt></del>
are valid until the completion handler for the asynchronous operation
is invoked.
</p>
</blockquote>
</li>

<li><p>Modify 17.9 [networking.ts::buffer.read.until], as indicated:</p>
<blockquote><pre>
template&lt;class SyncReadStream, class DynamicBuffer&gt;
  size_t read_until(SyncReadStream&amp; s, DynamicBuffer&amp;<del>&amp;</del> b, char delim);
template&lt;class SyncReadStream, class DynamicBuffer&gt;
  size_t read_until(SyncReadStream&amp; s, DynamicBuffer&amp;<del>&amp;</del> b,
                    char delim, error_code&amp; ec);
template&lt;class SyncReadStream, class DynamicBuffer&gt;
  size_t read_until(SyncReadStream&amp; s, DynamicBuffer&amp;<del>&amp;</del> b, string_view delim);
template&lt;class SyncReadStream, class DynamicBuffer&gt;
  size_t read_until(SyncReadStream&amp; s, DynamicBuffer&amp;<del>&amp;</del> b,
                    string_view delim, error_code&amp; ec);

[&hellip;]
</pre></blockquote>
</li>

<li><p>Modify 17.10 [networking.ts::buffer.async.read.until], as indicated:</p>
<blockquote><pre>
template&lt;class AsyncReadStream, class DynamicBuffer, class CompletionToken&gt;
  <i>DEDUCED</i> async_read_until(AsyncReadStream&amp; s,
                           DynamicBuffer&amp;<del>&amp;</del> b, char delim,
                           CompletionToken&amp;&amp; token);
template&lt;class AsyncReadStream, class DynamicBuffer, class CompletionToken&gt;
  <i>DEDUCED</i> async_read_until(AsyncReadStream&amp; s,
                           DynamicBuffer&amp;<del>&amp;</del> b, string_view delim,
                           CompletionToken&amp;&amp; token);

[&hellip;]
</pre>
<p>
-6- The program shall ensure <ins>both</ins> the <tt>AsyncReadStream</tt> object
<tt>stream</tt> <ins>and the <tt>DynamicBuffer</tt> object <tt>b</tt>
are</ins><del> is</del> valid until the completion handler for the
asynchronous operation is invoked.
</p>
</blockquote>
</li>
</ol>

<a name="References"></a><h2>References</h2>

[1] <a href="http://cplusplus.github.io/LWG/lwg-active.html#3072">http://cplusplus.github.io/LWG/lwg-active.html#3072</a><br>
[2] <a href="https://github.com/boostorg/beast">https://github.com/boostorg/beast</a><br>
</body>
</html>
