Add bind_front_handler:

This works similarly to bind_handler, and can help with reducing
template instantiations and compiler memory usage (since it does
not need to support placeholders).

A specialization for the most common case `void(error_code, size_t)`
is provided to minimize instantiation cost.
This commit is contained in:
Vinnie Falco
2018-11-21 11:17:33 -08:00
parent 868ca0f470
commit d581bcc348
6 changed files with 575 additions and 80 deletions

View File

@@ -1,3 +1,9 @@
Version 191:
* Add bind_front_handler
--------------------------------------------------------------------------------
Version 190:
* Add missing includes to convenience headers

View File

@@ -51,6 +51,17 @@ composed operations:
handler, whose associated allocator and associated executor will
will be the same as those of the original handler.
]]
[[
[link beast.ref.boost__beast__bind_front_handler `bind_front_handler`]
][
This function creates a new handler which, when invoked, calls
the original handler with the list of bound arguments, followed
by the list of invoked arguments. Placeholders are not supported.
The passed handler and arguments are forwarded into the returned
handler, whose associated allocator and associated executor will
will be the same as those of the original handler.
]]
[[
[link beast.ref.boost__beast__handler_ptr `handler_ptr`]
][

View File

@@ -213,6 +213,7 @@
<bridgehead renderas="sect3">Functions</bridgehead>
<simplelist type="vert" columns="1">
<member><link linkend="beast.ref.boost__beast__bind_handler">bind_handler</link></member>
<member><link linkend="beast.ref.boost__beast__bind_front_handler">bind_front_handler</link></member>
<member><link linkend="beast.ref.boost__beast__buffers">buffers</link></member>
<member><link linkend="beast.ref.boost__beast__buffers_cat">buffers_cat</link></member>
<member><link linkend="beast.ref.boost__beast__buffers_front">buffers_front</link></member>

View File

@@ -56,7 +56,7 @@ template<class Handler, class... Args>
#if BOOST_BEAST_DOXYGEN
__implementation_defined__
#else
detail::bound_handler<
detail::bind_wrapper<
typename std::decay<Handler>::type, Args...>
#endif
bind_handler(Handler&& handler, Args&&... args)
@@ -67,11 +67,57 @@ bind_handler(Handler&& handler, Args&&... args)
Handler, void(Args...)>::value,
"Handler requirements not met");
#endif
return detail::bound_handler<typename std::decay<
return detail::bind_wrapper<typename std::decay<
Handler>::type, Args...>(std::forward<
Handler>(handler), std::forward<Args>(args)...);
}
/** Bind parameters to a completion handler, creating a new handler.
This function creates a new handler which, when invoked, calls
the original handler with the list of bound arguments. Any
parameters passed in the invocation will be forwarded in
the parameter list after the bound arguments.
The passed handler and arguments are forwarded into the returned
handler, whose associated allocator and associated executor will
will be the same as those of the original handler.
Example:
@code
template<class AsyncReadStream, class ReadHandler>
void
signal_aborted(AsyncReadStream& stream, ReadHandler&& handler)
{
boost::asio::post(
stream.get_executor(),
bind_front_handler(std::forward<ReadHandler>(handler),
boost::asio::error::operation_aborted, 0));
}
@endcode
@param handler The handler to wrap.
@param args A list of arguments to bind to the handler.
The arguments are forwarded into the returned object.
*/
template<class Handler, class... Args>
#if BOOST_BEAST_DOXYGEN
__implementation_defined__
#else
auto
#endif
bind_front_handler(Handler&& handler, Args&&... args) ->
detail::bind_front_wrapper<typename
std::decay<Handler>::type, Args...>
{
return detail::bind_front_wrapper<typename
std::decay<Handler>::type, Args...>(
std::forward<Handler>(handler),
std::forward<Args>(args)...);
}
} // beast
} // boost

View File

@@ -10,12 +10,14 @@
#ifndef BOOST_BEAST_DETAIL_BIND_HANDLER_HPP
#define BOOST_BEAST_DETAIL_BIND_HANDLER_HPP
#include <boost/beast/core/error.hpp>
#include <boost/beast/core/detail/integer_sequence.hpp>
#include <boost/asio/associated_allocator.hpp>
#include <boost/asio/associated_executor.hpp>
#include <boost/asio/handler_continuation_hook.hpp>
#include <boost/asio/handler_invoke_hook.hpp>
#include <boost/core/ignore_unused.hpp>
#include <boost/mp11/integer_sequence.hpp>
#include <boost/is_placeholder.hpp>
#include <functional>
#include <utility>
@@ -24,25 +26,20 @@ namespace boost {
namespace beast {
namespace detail {
/* Nullary handler that calls Handler with bound arguments.
The bound handler provides the same io_context execution
guarantees as the original handler.
*/
template<class Handler, class... Args>
class bound_handler
class bind_wrapper
{
// Can't friend partial specializations,
// so we just friend the whole thing.
template<class T, class Executor>
friend struct boost::asio::associated_executor;
using args_type = std::tuple<
typename std::decay<Args>::type...>;
Handler h_;
args_type args_;
// Can't friend partial specializations,
// so we just friend the whole thing.
template<class T, class Executor>
friend struct boost::asio::associated_executor;
template<class Arg, class Vals>
static
typename std::enable_if<
@@ -95,13 +92,13 @@ class bound_handler
static
void
invoke(
Handler& h,
Handler& w,
ArgsTuple& args,
std::tuple<>&&,
index_sequence<S...>)
{
boost::ignore_unused(args);
h(std::get<S>(std::move(args))...);
w(std::get<S>(std::move(args))...);
}
template<
@@ -111,14 +108,14 @@ class bound_handler
static
void
invoke(
Handler& h,
Handler& w,
ArgsTuple& args,
ValsTuple&& vals,
index_sequence<S...>)
{
boost::ignore_unused(args);
boost::ignore_unused(vals);
h(extract(std::get<S>(std::move(args)),
w(extract(std::get<S>(std::move(args)),
std::forward<ValsTuple>(vals))...);
}
@@ -128,12 +125,12 @@ public:
using allocator_type =
boost::asio::associated_allocator_t<Handler>;
bound_handler(bound_handler&&) = default;
bound_handler(bound_handler const&) = delete;
bind_wrapper(bind_wrapper&&) = default;
bind_wrapper(bind_wrapper const&) = default;
template<class DeducedHandler>
explicit
bound_handler(
bind_wrapper(
DeducedHandler&& handler, Args&&... args)
: h_(std::forward<DeducedHandler>(handler))
, args_(std::forward<Args>(args)...)
@@ -148,18 +145,18 @@ public:
friend
bool
asio_handler_is_continuation(bound_handler* h)
asio_handler_is_continuation(bind_wrapper* w)
{
using boost::asio::asio_handler_is_continuation;
return asio_handler_is_continuation(std::addressof(h->h_));
return asio_handler_is_continuation(std::addressof(w->h_));
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, bound_handler* h)
void asio_handler_invoke(Function&& f, bind_wrapper* w)
{
using boost::asio::asio_handler_invoke;
asio_handler_invoke(f, std::addressof(h->h_));
asio_handler_invoke(f, std::addressof(w->h_));
}
template<class... Values>
@@ -183,35 +180,386 @@ public:
}
};
//------------------------------------------------------------------------------
template<class Handler, class... Args>
class bind_front_wrapper;
// 0-arg specialization
template<class Handler>
class bind_front_wrapper<Handler>
{
Handler h_;
// Can't friend partial specializations,
// so we just friend the whole thing.
template<class T, class Executor>
friend struct boost::asio::associated_executor;
public:
using result_type = void;
using allocator_type =
boost::asio::associated_allocator_t<Handler>;
bind_front_wrapper(bind_front_wrapper&&) = default;
bind_front_wrapper(bind_front_wrapper const&) = default;
template<class DeducedHandler>
explicit
bind_front_wrapper(DeducedHandler&& handler)
: h_(std::forward<DeducedHandler>(handler))
{
}
allocator_type
get_allocator() const noexcept
{
return boost::asio::get_associated_allocator(h_);
}
friend
bool
asio_handler_is_continuation(bind_front_wrapper* w)
{
using boost::asio::asio_handler_is_continuation;
return asio_handler_is_continuation(std::addressof(w->h_));
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, bind_front_wrapper* w)
{
using boost::asio::asio_handler_invoke;
asio_handler_invoke(f, std::addressof(w->h_));
}
template<class... Ts>
void operator()(Ts&&... ts)
{
h_(std::forward<Ts>(ts)...);
}
};
// 1-arg specialization
template<class Handler, class Arg>
class bind_front_wrapper<Handler, Arg>
{
Handler h_;
typename std::decay<Arg>::type arg_;
// Can't friend partial specializations,
// so we just friend the whole thing.
template<class T, class Executor>
friend struct boost::asio::associated_executor;
public:
using result_type = void;
using allocator_type =
boost::asio::associated_allocator_t<Handler>;
bind_front_wrapper(bind_front_wrapper&&) = default;
bind_front_wrapper(bind_front_wrapper const&) = default;
template<class DeducedHandler>
bind_front_wrapper(
DeducedHandler&& handler, Arg&& arg)
: h_(std::forward<DeducedHandler>(handler))
, arg_(std::forward<Arg>(arg))
{
}
allocator_type
get_allocator() const noexcept
{
return boost::asio::get_associated_allocator(h_);
}
friend
bool
asio_handler_is_continuation(bind_front_wrapper* w)
{
using boost::asio::asio_handler_is_continuation;
return asio_handler_is_continuation(std::addressof(w->h_));
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, bind_front_wrapper* w)
{
using boost::asio::asio_handler_invoke;
asio_handler_invoke(f, std::addressof(w->h_));
}
template<class... Ts>
void operator()(Ts&&... ts)
{
h_( std::forward<Arg>(arg_),
std::forward<Ts>(ts)...);
}
};
// 2-arg specialization
template<class Handler, class Arg1, class Arg2>
class bind_front_wrapper<Handler, Arg1, Arg2>
{
Handler h_;
typename std::decay<Arg1>::type arg1_;
typename std::decay<Arg2>::type arg2_;
// Can't friend partial specializations,
// so we just friend the whole thing.
template<class T, class Executor>
friend struct boost::asio::associated_executor;
public:
using result_type = void;
using allocator_type =
boost::asio::associated_allocator_t<Handler>;
bind_front_wrapper(bind_front_wrapper&&) = default;
bind_front_wrapper(bind_front_wrapper const&) = default;
template<class DeducedHandler>
bind_front_wrapper(DeducedHandler&& handler,
Arg1&& arg1, Arg2&& arg2)
: h_(std::forward<DeducedHandler>(handler))
, arg1_(std::forward<Arg1>(arg1))
, arg2_(std::forward<Arg2>(arg2))
{
}
allocator_type
get_allocator() const noexcept
{
return boost::asio::get_associated_allocator(h_);
}
friend
bool
asio_handler_is_continuation(bind_front_wrapper* w)
{
using boost::asio::asio_handler_is_continuation;
return asio_handler_is_continuation(std::addressof(w->h_));
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, bind_front_wrapper* w)
{
using boost::asio::asio_handler_invoke;
asio_handler_invoke(f, std::addressof(w->h_));
}
template<class... Ts>
void operator()(Ts&&... ts)
{
h_( std::forward<Arg1>(arg1_),
std::forward<Arg2>(arg2_),
std::forward<Ts>(ts)...);
}
};
// 3+ arg specialization
template<class Handler,
class Arg1, class Arg2, class Arg3, class... Args>
class bind_front_wrapper<Handler, Arg1, Arg2, Arg3, Args...>
{
using args_type = std::tuple<
typename std::decay<Arg1>::type,
typename std::decay<Arg2>::type,
typename std::decay<Arg3>::type,
typename std::decay<Args>::type...>;
Handler h_;
args_type args_;
// Can't friend partial specializations,
// so we just friend the whole thing.
template<class T, class Executor>
friend struct boost::asio::associated_executor;
template<std::size_t... I, class... Ts>
void
invoke(
boost::mp11::index_sequence<I...>,
Ts&&... ts)
{
h_( std::get<I>(std::move(args_))...,
std::forward<Ts>(ts)...);
}
public:
using result_type = void;
using allocator_type =
boost::asio::associated_allocator_t<Handler>;
bind_front_wrapper(bind_front_wrapper&&) = default;
bind_front_wrapper(bind_front_wrapper const&) = default;
template<class DeducedHandler>
bind_front_wrapper(DeducedHandler&& handler,
Arg1&& arg1, Arg2&& arg2, Arg3&& arg3,
Args&&... args)
: h_(std::forward<DeducedHandler>(handler))
, args_(
std::forward<Arg1>(arg1),
std::forward<Arg2>(arg2),
std::forward<Arg3>(arg3),
std::forward<Args>(args)...)
{
}
allocator_type
get_allocator() const noexcept
{
return boost::asio::get_associated_allocator(h_);
}
friend
bool
asio_handler_is_continuation(bind_front_wrapper* w)
{
using boost::asio::asio_handler_is_continuation;
return asio_handler_is_continuation(std::addressof(w->h_));
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, bind_front_wrapper* w)
{
using boost::asio::asio_handler_invoke;
asio_handler_invoke(f, std::addressof(w->h_));
}
template<class... Ts>
void operator()(Ts&&... ts)
{
invoke(
boost::mp11::index_sequence_for<
Arg1, Arg2, Arg3, Args...>{},
std::forward<Ts>(ts)...);
}
};
//------------------------------------------------------------------------------
// specialization for the most common case,
// to reduce instantiation time and memory.
template<class Handler>
class bind_front_wrapper<
Handler, error_code, std::size_t>
{
Handler h_;
error_code ec_;
std::size_t n_;
public:
using result_type = void;
using allocator_type =
boost::asio::associated_allocator_t<Handler>;
bind_front_wrapper(bind_front_wrapper&&) = default;
bind_front_wrapper(bind_front_wrapper const&) = default;
template<class DeducedHandler>
bind_front_wrapper(DeducedHandler&& handler,
error_code ec, std::size_t n)
: h_(std::forward<DeducedHandler>(handler))
, ec_(ec)
, n_(n)
{
}
allocator_type
get_allocator() const noexcept
{
return boost::asio::get_associated_allocator(h_);
}
friend
bool
asio_handler_is_continuation(bind_front_wrapper* w)
{
using boost::asio::asio_handler_is_continuation;
return asio_handler_is_continuation(std::addressof(w->h_));
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, bind_front_wrapper* w)
{
using boost::asio::asio_handler_invoke;
asio_handler_invoke(f, std::addressof(w->h_));
}
void operator()()
{
h_(ec_, n_);
}
};
} // detail
} // beast
//------------------------------------------------------------------------------
namespace asio {
template<class Handler, class... Args, class Executor>
struct associated_executor<
beast::detail::bound_handler<Handler, Args...>, Executor>
beast::detail::bind_wrapper<Handler, Args...>, Executor>
{
using type = typename
associated_executor<Handler, Executor>::type;
static
type
get(beast::detail::bound_handler<Handler, Args...> const& h,
get(beast::detail::bind_wrapper<Handler, Args...> const& w,
Executor const& ex = Executor()) noexcept
{
return associated_executor<
Handler, Executor>::get(h.h_, ex);
Handler, Executor>::get(w.h_, ex);
}
};
template<class Handler, class... Args, class Executor>
struct associated_executor<
beast::detail::bind_front_wrapper<Handler, Args...>, Executor>
{
using type = typename
associated_executor<Handler, Executor>::type;
static
type
get(beast::detail::bind_front_wrapper<Handler, Args...> const& w,
Executor const& ex = Executor()) noexcept
{
return associated_executor<
Handler, Executor>::get(w.h_, ex);
}
};
} // asio
} // boost
namespace std {
template<class Handler, class... Args>
void
bind(boost::beast::detail::bound_handler<
bind(boost::beast::detail::bind_wrapper<
Handler, Args...>, ...) = delete;
template<class Handler, class... Args>
void
bind(boost::beast::detail::bind_front_wrapper<
Handler, Args...>, ...) = delete;
} // std
#endif

View File

@@ -32,34 +32,7 @@ public:
}
};
#if 0
// This function should fail to compile
void
failStdBind()
{
std::bind(bind_handler(handler<>{}));
}
#endif
void
callback(int v)
{
BEAST_EXPECT(v == 42);
}
void
testPlaceholders()
{
namespace ph = std::placeholders;
bind_handler(handler<>{})();
bind_handler(handler<int>{}, 1)();
bind_handler(handler<int, std::string>{}, 1, "hello")();
bind_handler(handler<int>{}, ph::_1)(1);
bind_handler(handler<int, std::string>{}, ph::_1, ph::_2)(1, "hello");
}
struct copyable_handler
struct copyable
{
template<class... Args>
void
@@ -68,44 +41,154 @@ public:
}
};
void
testAsioHandlerInvoke()
{
// make sure things compile, also can set a
// breakpoint in asio_handler_invoke to make sure
// it is instantiated.
boost::asio::io_context ioc;
boost::asio::io_service::strand s{ioc};
test::stream ts{ioc};
boost::asio::post(ioc.get_executor(),
s.wrap(copyable_handler{}));
}
struct move_only
{
move_only() = default;
move_only(move_only&&) = default;
move_only(move_only const&) = delete;
void operator()() const{};
};
void
testMoveOnly()
callback(int v)
{
bind_handler([](move_only){}, move_only{})();
bind_handler([](move_only){}, std::placeholders::_1)(move_only{});
bind_handler([](move_only, move_only){}, move_only{}, std::placeholders::_1)(move_only{});
BEAST_EXPECT(v == 42);
}
#if 0
// This function should fail to compile
void
failStdBind()
{
std::bind(bind_handler(handler<>{}));
}
void
failStdBindFront()
{
std::bind(bind_front_handler(handler<>{}));
}
#endif
void
testBindHandler()
{
// invocation
{
auto f = bind_handler(std::bind(
&bind_handler_test::callback, this,
std::placeholders::_1), 42);
f();
}
// placeholders
{
namespace ph = std::placeholders;
bind_handler(handler<>{})();
bind_handler(handler<int>{}, 1)();
bind_handler(handler<int, std::string>{}, 1, "hello")();
bind_handler(handler<int>{}, ph::_1)(1);
bind_handler(handler<int, std::string>{}, ph::_1, ph::_2)(1, "hello");
}
// move-only
{
bind_handler([](move_only){}, move_only{})();
bind_handler([](move_only){}, std::placeholders::_1)(move_only{});
bind_handler([](move_only, move_only){}, move_only{}, std::placeholders::_1)(move_only{});
}
// asio_handler_invoke
{
// make sure things compile, also can set a
// breakpoint in asio_handler_invoke to make sure
// it is instantiated.
boost::asio::io_context ioc;
boost::asio::strand<
boost::asio::io_context::executor_type> s{
ioc.get_executor()};
test::stream ts{ioc};
boost::asio::post(s,
bind_handler(copyable{}, 42));
}
}
void
testBindFrontHandler()
{
// invocation
{
bind_front_handler(
std::bind(
&bind_handler_test::callback,
this,
42));
bind_front_handler(
std::bind(
&bind_handler_test::callback,
this,
std::placeholders::_1), 42);
bind_front_handler(
std::bind(
&bind_handler_test::callback,
this,
std::placeholders::_1))(42);
bind_front_handler(
bind_front_handler(
std::bind(
&bind_handler_test::callback,
this,
std::placeholders::_1)),
42);
bind_front_handler(
bind_front_handler(
std::bind(
&bind_handler_test::callback,
this,
std::placeholders::_1)))(42);
}
// move-only
{
bind_front_handler([]{});
}
// specializations
{
bind_front_handler(copyable{});
bind_front_handler(copyable{}, 1);
bind_front_handler(copyable{}, 1, 2);
bind_front_handler(copyable{}, 1, 2, 3);
bind_front_handler(copyable{}, 1, 2, 3, 4);
bind_front_handler(copyable{},
error_code{}, std::size_t(4));
}
// asio_handler_invoke
{
// make sure things compile, also can set a
// breakpoint in asio_handler_invoke to make sure
// it is instantiated.
boost::asio::io_context ioc;
boost::asio::strand<
boost::asio::io_context::executor_type> s{
ioc.get_executor()};
test::stream ts{ioc};
boost::asio::post(s,
bind_front_handler(copyable{}, 42));
}
}
void
run() override
{
auto f = bind_handler(std::bind(
&bind_handler_test::callback, this,
std::placeholders::_1), 42);
f();
testPlaceholders();
testAsioHandlerInvoke();
testMoveOnly();
testBindHandler();
testBindFrontHandler();
}
};