What you expect exists. It's in the DynamicBuffer concept.
read|wrte_some
member functions are low-level and do not take dynamic buffers (it takes a sequence of MutableBuffer). In fact, I maintain that using these functions is not what you want 99% of the time. E.g. see this remark:
Remarks
The read operation may not read all of the requested number of bytes. Consider using the async_read function if you need to ensure that the requested amount of data is read before the asynchronous operation completes
People often have wrong assumptions about IO packet delivery. Just use the async_read
function instead, with your dynamic buffer:
Doing It Yourself
Of course, you can use a dynamic buffer (i.e. with read/write "cursors") with any lowlevel interfaces (that expect straight buffers). But then you have to use the prepare()
/consume()
/commit()
interfaces to manage the cursors (and optionally allocations that may happen in the underlying storage).
You could compare this with iostreams
: Instead of int x; std::cin >> x;
you can very much use the underlying streambuf interface, but it will just be more work and error-prone.
Of course, sometimes you want that lowlevel interface (e.g. when you want to be able to know more detailed what packets arrive at what time), but I think that's the exception to the rule.
BONUS: Demo code
The simplest fix to your question code is to use the composed write operation:
void writing(std::vector<const_buffer> const& buffers, strand& strand)
{
/* io worker may call back at arbitrary amount of transferred bytes */
boost::asio::async_write(
socket, buffers,
bind_executor(strand, [self = shared_from_this(), strand]
(error_code ec, size_t transferred) {
std::cout << "Transferred " << transferred << " bytes "
<< "(" << ec.message() << ")" << std::endl;
}));
}
Here the "cursoring" is done by the library. No questions asked.
Notes:
you might be more accepting of the buffer type, accepting any model of DynamicBuffer (v1/v2) and ConstBufferSequence:
template <typename Buffers>
void writing(Buffers&& buffers, strand& strand)
{
/* io worker may call back at arbitrary amount of transferred bytes */
boost::asio::async_write(
socket, std::forward<Buffers>(buffers),
bind_executor(strand, [self = shared_from_this(), strand]
(error_code ec, size_t transferred) {
std::cout << "Transferred " << transferred << " bytes "
<< "(" << ec.message() << ")" << std::endl;
}));
}
You could associate the executor with the IO object, e.g. like this:
koebenhavn(boost::asio::io_context& ctx)
: socket_(make_strand(ctx))
{ }
Now you can use that (boost::asio::associated_executor(socket_)
or socket_.get_executor()
or just let the library do that by default when you initiate any async operation on that socket.
template <typename Buffers>
void writing(Buffers&& buffers)
{
/* io worker may call back at arbitrary amount of transferred bytes */
async_write(
socket, std::forward<Buffers>(buffers),
[self = shared_from_this()](error_code ec, size_t transferred) {
std::cout << "Transferred " << transferred << " bytes "
<< "(" << ec.message() << ")" << std::endl;
});
}
Notice things got simple? Time to complicate it back up. Your code has the tacit assumption that writing(...)
will be called safely, i.e. from the strand. Since we don't know that for sure, consider posting or dispatching to the strand:
dispatch( //
socket_.get_executor(),
[this, self, b = std::forward_as_tuple(buffers)]() mutable {
async_write( //
socket_, std::get<0>(b),
[self](error_code ec, size_t transferred) {
std::cout << "Transferred " << transferred << " bytes "
<< "(" << ec.message() << ")" << std::endl;
});
});
Using dispatch
has the advantage that the library may be able to run the task immediately if it detects that you're already on the strand.
Live Demo
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
class koebenhavn : public std::enable_shared_from_this<koebenhavn> {
private:
boost::asio::ip::tcp::socket socket_;
using const_buffer = boost::asio::const_buffer;
using error_code = boost::system::error_code;
using strand = boost::asio::io_context::strand;
public:
koebenhavn(boost::asio::io_context& ctx)
: socket_(make_strand(ctx))
{ }
// io worker may call back at arbitrary amount of transferred bytes
template <typename Buffers> void writing(Buffers&& buffers)
{
auto self = shared_from_this();
dispatch( //
socket_.get_executor(),
[this, self, b = std::forward_as_tuple(buffers)]() mutable {
async_write( //
socket_, std::get<0>(b),
[self](error_code ec, size_t transferred) {
std::cout << "Transferred " << transferred << " bytes "
<< "(" << ec.message() << ")" << std::endl;
});
});
}
};
int main() {
boost::asio::io_context io;
auto k = std::make_shared<koebenhavn>(io);
boost::asio::streambuf sb;
k->writing(sb);
std::string s = "hello world\n";
k->writing(boost::asio::buffer(s));
std::array<float, 7> ff{};
std::vector<unsigned char> bb{{0, 1, 2, 3, 4}};
k->writing(std::vector{
boost::asio::buffer(ff),
boost::asio::buffer(bb),
});
}