forked from boostorg/beast
* Address bind_handler ambiguous overload error when boost/bind/std_placeholders.hpp is included
Previously, the implementation of bind_handler made the assumption
that the trait boost::is_placeholder would be false for types
corresponding to values in the std::placeholders namespace.
However, boost/bind commit c85b31e3d200dda2a73cf0027a82c6d8e29066f8,
`Support use of standard placeholders with boost::bind`, added a new
header, boost/bind/std_placeholders.hpp, which adds specializations to
boost::is_placeholder for std::placeholders types.
When this header is included before a use of boost::bind_handler, it
results in compiler errors like the following, due to an internal
helper function having overloads for
enable_if<boost::is_placeholder...> and
enable_if<std::is_placeholder...>, which were previously assumed to be
mutually exclusive, but for which both conditions now are true:
../../../boost/beast/core/detail/bind_handler.hpp:126:18: error: call of overloaded 'extract(std::_Placeholder<1>, boost::beast::detail::tuple<int&&>)' is ambiguous
126 | h(extract(detail::get<S>(std::move(args)),
| ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
127 | std::forward<ValsTuple>(vals))...);
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This header is included in boost/bind/bind.hpp, and is transitively
included by headers in many boost libraries including:
algorithm
asio
atomic
graph
msm
multi_index
property_tree
ptr_container
python
signals2
test
thread
Making it possible that an include from one of these libraries will
randomly cause bind_handler to fail to compile.
This change addresses the issue by eliminating one of the ambiguous
overloads and ensuring that boost/bind/std_placeholders.hpp is
included in the bind_handler implementation file, so that the
enable_if<boost::is_placeholder...> overload can handle both
boost::placeholders and std::placeholders values. It also explicitly
adds a boost/bind/std_placeholders.hpp include to the bind_handler
unit test to prevent this issue from regressing.
* Address boost/bind/std_placeholders.hpp feature guards
boost/bind/std_placeholders.hpp has the following feature guards:
if !defined(BOOST_NO_CXX11_HDR_FUNCTIONAL) && !defined(BOOST_NO_CXX11_DECLTYPE) && !defined(BOOST_NO_CXX11_HDR_TYPE_TRAITS)
If this check fails, it will not detect std::placeholders values as
placeholders, making the previous solution fail.
This update restores the std::is_placeholder overload and adjusts the
SFINAE for the boost::is_placeholder overload so that it triggers only
if std::is_placeholder is false, making bind_handler work regardless
of whether std_placeholders.hpp's feature guard applies.
---------
Co-authored-by: Edward Nolan <enolan@maystreet.com>
629 lines
16 KiB
C++
629 lines
16 KiB
C++
//
|
|
// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
|
|
//
|
|
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
//
|
|
// Official repository: https://github.com/boostorg/beast
|
|
//
|
|
|
|
// Test that header file is self-contained.
|
|
#include <boost/beast/core/bind_handler.hpp>
|
|
|
|
#include "test_handler.hpp"
|
|
|
|
#include <boost/beast/core/detail/type_traits.hpp>
|
|
#include <boost/beast/_experimental/unit_test/suite.hpp>
|
|
#include <boost/beast/_experimental/test/stream.hpp>
|
|
#include <boost/asio/io_context.hpp>
|
|
#include <boost/asio/bind_executor.hpp>
|
|
#include <boost/asio/executor_work_guard.hpp>
|
|
#include <boost/asio/dispatch.hpp>
|
|
#include <boost/asio/defer.hpp>
|
|
#include <boost/asio/post.hpp>
|
|
#include <boost/asio/strand.hpp>
|
|
#include <boost/bind/placeholders.hpp>
|
|
#include <boost/bind/std_placeholders.hpp>
|
|
#include <boost/core/exchange.hpp>
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
namespace boost {
|
|
namespace beast {
|
|
|
|
class bind_handler_test : public unit_test::suite
|
|
{
|
|
public:
|
|
template<class... Args>
|
|
struct handler
|
|
{
|
|
void
|
|
operator()(Args const&...) const
|
|
{
|
|
}
|
|
};
|
|
|
|
struct copyable
|
|
{
|
|
template<class... Args>
|
|
void
|
|
operator()(Args&&...) const
|
|
{
|
|
}
|
|
};
|
|
|
|
struct move_only
|
|
{
|
|
move_only() = default;
|
|
move_only(move_only&&) = default;
|
|
move_only(move_only const&) = delete;
|
|
void operator()() const{};
|
|
};
|
|
|
|
// A move-only parameter
|
|
template<std::size_t I>
|
|
struct move_arg
|
|
{
|
|
move_arg() = default;
|
|
move_arg(move_arg&&) = default;
|
|
move_arg(move_arg const&) = delete;
|
|
void operator()() const
|
|
{
|
|
};
|
|
};
|
|
|
|
void
|
|
callback(int v)
|
|
{
|
|
BEAST_EXPECT(v == 42);
|
|
}
|
|
|
|
class test_executor
|
|
{
|
|
bind_handler_test& s_;
|
|
|
|
#if defined(BOOST_ASIO_NO_TS_EXECUTORS)
|
|
net::any_io_executor ex_;
|
|
|
|
// Storing the blocking property as a member is not strictly necessary,
|
|
// as we could simply forward the calls
|
|
// require(ex_, blocking.possibly)
|
|
// and
|
|
// require(ex_, blocking.never)
|
|
// to the underlying executor, and then
|
|
// query(ex_, blocking)
|
|
// when required. This forwarding approach is used here for the
|
|
// outstanding_work property.
|
|
net::execution::blocking_t blocking_;
|
|
|
|
#else // defined(BOOST_ASIO_NO_TS_EXECUTORS)
|
|
net::io_context::executor_type ex_;
|
|
#endif // defined(BOOST_ASIO_NO_TS_EXECUTORS)
|
|
public:
|
|
test_executor(
|
|
test_executor const&) = default;
|
|
|
|
test_executor(
|
|
bind_handler_test& s,
|
|
net::io_context& ioc)
|
|
: s_(s)
|
|
, ex_(ioc.get_executor())
|
|
#if defined(BOOST_ASIO_NO_TS_EXECUTORS)
|
|
, blocking_(net::execution::blocking.possibly)
|
|
#endif
|
|
{
|
|
}
|
|
|
|
bool operator==(
|
|
test_executor const& other) const noexcept
|
|
{
|
|
return ex_ == other.ex_;
|
|
}
|
|
|
|
bool operator!=(
|
|
test_executor const& other) const noexcept
|
|
{
|
|
return ex_ != other.ex_;
|
|
}
|
|
|
|
#if defined(BOOST_ASIO_NO_TS_EXECUTORS)
|
|
|
|
net::execution_context&
|
|
query(
|
|
net::execution::context_t c) const noexcept
|
|
{
|
|
return net::query(ex_, c);
|
|
}
|
|
|
|
net::execution::blocking_t
|
|
query(
|
|
net::execution::blocking_t) const noexcept
|
|
{
|
|
return blocking_;
|
|
}
|
|
|
|
net::execution::outstanding_work_t
|
|
query(
|
|
net::execution::outstanding_work_t w) const noexcept
|
|
{
|
|
return net::query(ex_, w);
|
|
}
|
|
|
|
test_executor
|
|
require(
|
|
net::execution::blocking_t::possibly_t b) const
|
|
{
|
|
test_executor new_ex(*this);
|
|
new_ex.blocking_ = b;
|
|
return new_ex;
|
|
}
|
|
|
|
test_executor
|
|
require(
|
|
net::execution::blocking_t::never_t b) const
|
|
{
|
|
test_executor new_ex(*this);
|
|
new_ex.blocking_ = b;
|
|
return new_ex;
|
|
}
|
|
|
|
test_executor prefer(net::execution::outstanding_work_t::untracked_t w) const
|
|
{
|
|
test_executor new_ex(*this);
|
|
new_ex.ex_ = net::prefer(ex_, w);
|
|
return new_ex;
|
|
}
|
|
|
|
test_executor prefer(net::execution::outstanding_work_t::tracked_t w) const
|
|
{
|
|
test_executor new_ex(*this);
|
|
new_ex.ex_ = net::prefer(ex_, w);
|
|
return new_ex;
|
|
}
|
|
|
|
template<class F>
|
|
void execute(F&& f) const
|
|
{
|
|
if (blocking_ == net::execution::blocking.possibly)
|
|
{
|
|
s_.on_invoke();
|
|
ex_.execute(std::forward<F>(f));
|
|
}
|
|
else
|
|
{
|
|
// shouldn't be called since the enclosing
|
|
// networking wrapper only uses dispatch
|
|
BEAST_FAIL();
|
|
}
|
|
}
|
|
#endif
|
|
#if !defined(BOOST_ASIO_NO_TS_EXECUTORS)
|
|
net::execution_context&
|
|
context() const noexcept
|
|
{
|
|
return ex_.context();
|
|
}
|
|
|
|
void
|
|
on_work_started() const noexcept
|
|
{
|
|
ex_.on_work_started();
|
|
}
|
|
|
|
void
|
|
on_work_finished() const noexcept
|
|
{
|
|
ex_.on_work_finished();
|
|
}
|
|
|
|
template<class F, class Alloc>
|
|
void
|
|
dispatch(F&& f, Alloc const& a)
|
|
{
|
|
s_.on_invoke();
|
|
net::prefer(ex_,
|
|
net::execution::blocking.possibly,
|
|
net::execution::allocator(a)).execute(std::forward<F>(f));
|
|
// previously equivalent to
|
|
// ex_.dispatch(std::forward<F>(f), a);
|
|
}
|
|
|
|
template<class F, class Alloc>
|
|
void
|
|
post(F&& f, Alloc const& a)
|
|
{
|
|
// shouldn't be called since the enclosing
|
|
// networking wrapper only uses dispatch
|
|
BEAST_FAIL();
|
|
}
|
|
|
|
template<class F, class Alloc>
|
|
void
|
|
defer(F&& f, Alloc const& a)
|
|
{
|
|
// shouldn't be called since the enclosing
|
|
// networking wrapper only uses dispatch
|
|
BEAST_FAIL();
|
|
}
|
|
#endif // !defined(BOOST_ASIO_NO_TS_EXECUTORS)
|
|
};
|
|
|
|
#if defined(BOOST_ASIO_NO_TS_EXECUTORS)
|
|
BOOST_STATIC_ASSERT(net::execution::is_executor<test_executor>::value);
|
|
#else
|
|
BOOST_STATIC_ASSERT(net::is_executor<test_executor>::value);
|
|
#endif
|
|
|
|
class test_cb
|
|
{
|
|
bool fail_ = true;
|
|
|
|
public:
|
|
test_cb() = default;
|
|
test_cb(test_cb const&) = delete;
|
|
test_cb(test_cb&& other)
|
|
: fail_(boost::exchange(
|
|
other.fail_, false))
|
|
{
|
|
}
|
|
|
|
~test_cb()
|
|
{
|
|
BEAST_EXPECT(! fail_);
|
|
}
|
|
|
|
void
|
|
operator()()
|
|
{
|
|
fail_ = false;
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
operator()(int v)
|
|
{
|
|
fail_ = false;
|
|
BEAST_EXPECT(v == 42);
|
|
}
|
|
|
|
void
|
|
operator()(int v, string_view s)
|
|
{
|
|
fail_ = false;
|
|
BEAST_EXPECT(v == 42);
|
|
BEAST_EXPECT(s == "s");
|
|
}
|
|
|
|
void
|
|
operator()(int v, string_view s, move_arg<1>)
|
|
{
|
|
fail_ = false;
|
|
BEAST_EXPECT(v == 42);
|
|
BEAST_EXPECT(s == "s");
|
|
}
|
|
|
|
void
|
|
operator()(int v, string_view s, move_arg<1>, move_arg<2>)
|
|
{
|
|
fail_ = false;
|
|
BEAST_EXPECT(v == 42);
|
|
BEAST_EXPECT(s == "s");
|
|
}
|
|
|
|
void
|
|
operator()(error_code, std::size_t n)
|
|
{
|
|
fail_ = false;
|
|
BEAST_EXPECT(n == 256);
|
|
}
|
|
|
|
void
|
|
operator()(
|
|
error_code, std::size_t n, string_view s)
|
|
{
|
|
boost::ignore_unused(s);
|
|
fail_ = false;
|
|
BEAST_EXPECT(n == 256);
|
|
}
|
|
|
|
void
|
|
operator()(std::shared_ptr<int> const& sp)
|
|
{
|
|
fail_ = false;
|
|
BEAST_EXPECT(sp.get() != nullptr);
|
|
}
|
|
};
|
|
|
|
#if 0
|
|
// These functions should fail to compile
|
|
|
|
void
|
|
failStdBind()
|
|
{
|
|
std::bind(bind_handler(test_cb{}));
|
|
}
|
|
|
|
void
|
|
failStdBindFront()
|
|
{
|
|
std::bind(bind_front_handler(test_cb{}));
|
|
}
|
|
#endif
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
bool invoked_;
|
|
|
|
void
|
|
on_invoke()
|
|
{
|
|
invoked_ = true;
|
|
}
|
|
|
|
template<class F>
|
|
void
|
|
testHooks(net::io_context& ioc, F&& f)
|
|
{
|
|
invoked_ = false;
|
|
net::post(ioc, std::forward<F>(f));
|
|
ioc.run();
|
|
ioc.restart();
|
|
BEAST_EXPECT(invoked_);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
void
|
|
testBindHandler()
|
|
{
|
|
using m1 = move_arg<1>;
|
|
using m2 = move_arg<2>;
|
|
|
|
{
|
|
using namespace std::placeholders;
|
|
|
|
// 0-ary
|
|
bind_handler(test_cb{})();
|
|
|
|
// 1-ary
|
|
bind_handler(test_cb{}, 42)();
|
|
bind_handler(test_cb{}, _1)(42);
|
|
bind_handler(test_cb{}, _2)(0, 42);
|
|
|
|
// 2-ary
|
|
bind_handler(test_cb{}, 42, "s")();
|
|
bind_handler(test_cb{}, 42, "s")(0);
|
|
bind_handler(test_cb{}, _1, "s")(42);
|
|
bind_handler(test_cb{}, 42, _1) ("s");
|
|
bind_handler(test_cb{}, _1, _2)(42, "s");
|
|
bind_handler(test_cb{}, _1, _2)(42, "s", "X");
|
|
bind_handler(test_cb{}, _2, _1)("s", 42);
|
|
bind_handler(test_cb{}, _3, _2)("X", "s", 42);
|
|
|
|
// 3-ary
|
|
bind_handler(test_cb{}, 42, "s")(m1{});
|
|
bind_handler(test_cb{}, 42, "s", _1)(m1{});
|
|
bind_handler(test_cb{}, 42, _1, m1{})("s");
|
|
|
|
// 4-ary
|
|
bind_handler(test_cb{}, 42, "s")(m1{}, m2{});
|
|
bind_handler(test_cb{}, 42, "s", m1{})(m2{});
|
|
bind_handler(test_cb{}, 42, "s", m1{}, m2{})();
|
|
bind_handler(test_cb{}, 42, _1, m1{})("s", m2{});
|
|
bind_handler(test_cb{}, _3, _1, m1{})("s", m2{}, 42);
|
|
}
|
|
|
|
{
|
|
using namespace boost::placeholders;
|
|
|
|
// 0-ary
|
|
bind_handler(test_cb{})();
|
|
|
|
// 1-ary
|
|
bind_handler(test_cb{}, 42)();
|
|
bind_handler(test_cb{}, _1)(42);
|
|
bind_handler(test_cb{}, _2)(0, 42);
|
|
|
|
// 2-ary
|
|
bind_handler(test_cb{}, 42, "s")();
|
|
bind_handler(test_cb{}, 42, "s")(0);
|
|
bind_handler(test_cb{}, _1, "s")(42);
|
|
bind_handler(test_cb{}, 42, _1) ("s");
|
|
bind_handler(test_cb{}, _1, _2)(42, "s");
|
|
bind_handler(test_cb{}, _1, _2)(42, "s", "X");
|
|
bind_handler(test_cb{}, _2, _1)("s", 42);
|
|
bind_handler(test_cb{}, _3, _2)("X", "s", 42);
|
|
|
|
// 3-ary
|
|
bind_handler(test_cb{}, 42, "s")(m1{});
|
|
bind_handler(test_cb{}, 42, "s", _1)(m1{});
|
|
bind_handler(test_cb{}, 42, _1, m1{})("s");
|
|
|
|
// 4-ary
|
|
bind_handler(test_cb{}, 42, "s")(m1{}, m2{});
|
|
bind_handler(test_cb{}, 42, "s", m1{})(m2{});
|
|
bind_handler(test_cb{}, 42, "s", m1{}, m2{})();
|
|
bind_handler(test_cb{}, 42, _1, m1{})("s", m2{});
|
|
bind_handler(test_cb{}, _3, _1, m1{})("s", m2{}, 42);
|
|
}
|
|
|
|
// perfect forwarding
|
|
{
|
|
std::shared_ptr<int> const sp =
|
|
std::make_shared<int>(42);
|
|
{
|
|
bind_handler(test_cb{}, sp)();
|
|
BEAST_EXPECT(sp.get() != nullptr);
|
|
}
|
|
{
|
|
bind_handler(test_cb{})(sp);
|
|
BEAST_EXPECT(sp.get() != nullptr);
|
|
}
|
|
}
|
|
|
|
// associated executor
|
|
{
|
|
net::io_context ioc;
|
|
testHooks(ioc, bind_handler(net::bind_executor(
|
|
test_executor(*this, ioc), test_cb{})));
|
|
}
|
|
|
|
// asio_handler_invoke
|
|
{
|
|
// make sure things compile, also can set a
|
|
// breakpoint in asio_handler_invoke to make sure
|
|
// it is instantiated.
|
|
net::io_context ioc;
|
|
net::strand<
|
|
net::io_context::executor_type> s{
|
|
ioc.get_executor()};
|
|
net::post(s,
|
|
bind_handler(test_cb{}, 42));
|
|
ioc.run();
|
|
}
|
|
|
|
// legacy hooks
|
|
legacy_handler::test(
|
|
[](legacy_handler h)
|
|
{
|
|
return bind_handler(h);
|
|
});
|
|
}
|
|
|
|
void
|
|
testBindFrontHandler()
|
|
{
|
|
using m1 = move_arg<1>;
|
|
using m2 = move_arg<2>;
|
|
|
|
// 0-ary
|
|
bind_front_handler(test_cb{})();
|
|
|
|
// 1-ary
|
|
bind_front_handler(test_cb{}, 42)();
|
|
bind_front_handler(test_cb{})(42);
|
|
|
|
// 2-ary
|
|
bind_front_handler(test_cb{}, 42, "s")();
|
|
bind_front_handler(test_cb{}, 42)("s");
|
|
bind_front_handler(test_cb{})(42, "s");
|
|
|
|
// 3-ary
|
|
bind_front_handler(test_cb{}, 42, "s", m1{})();
|
|
bind_front_handler(test_cb{}, 42, "s")(m1{});
|
|
bind_front_handler(test_cb{}, 42)("s", m1{});
|
|
bind_front_handler(test_cb{})(42, "s", m1{});
|
|
|
|
// 4-ary
|
|
bind_front_handler(test_cb{}, 42, "s", m1{}, m2{})();
|
|
bind_front_handler(test_cb{}, 42, "s", m1{})(m2{});
|
|
bind_front_handler(test_cb{}, 42, "s")(m1{}, m2{});
|
|
bind_front_handler(test_cb{}, 42)("s", m1{}, m2{});
|
|
bind_front_handler(test_cb{})(42, "s", m1{}, m2{});
|
|
|
|
error_code ec;
|
|
std::size_t n = 256;
|
|
|
|
// void(error_code, size_t)
|
|
bind_front_handler(test_cb{}, ec, n)();
|
|
|
|
// void(error_code, size_t)(string_view)
|
|
bind_front_handler(test_cb{}, ec, n)("s");
|
|
|
|
// perfect forwarding
|
|
{
|
|
std::shared_ptr<int> const sp =
|
|
std::make_shared<int>(42);
|
|
bind_front_handler(test_cb{}, sp)();
|
|
BEAST_EXPECT(sp.get() != nullptr);
|
|
}
|
|
|
|
// associated executor
|
|
{
|
|
net::io_context ioc;
|
|
|
|
testHooks(ioc, bind_front_handler(net::bind_executor(
|
|
test_executor(*this, ioc), test_cb{})
|
|
));
|
|
testHooks(ioc, bind_front_handler(net::bind_executor(
|
|
test_executor(*this, ioc), test_cb{}),
|
|
42));
|
|
testHooks(ioc, bind_front_handler(net::bind_executor(
|
|
test_executor(*this, ioc), test_cb{}),
|
|
42, "s"));
|
|
testHooks(ioc, bind_front_handler(net::bind_executor(
|
|
test_executor(*this, ioc), test_cb{}),
|
|
42, "s", m1{}));
|
|
testHooks(ioc, bind_front_handler(net::bind_executor(
|
|
test_executor(*this, ioc), test_cb{}),
|
|
42, "s", m1{}, m2{}));
|
|
testHooks(ioc, bind_front_handler(net::bind_executor(
|
|
test_executor(*this, ioc), test_cb{}),
|
|
ec, n));
|
|
}
|
|
|
|
// legacy hooks
|
|
legacy_handler::test(
|
|
[](legacy_handler h)
|
|
{
|
|
return bind_front_handler(h);
|
|
});
|
|
legacy_handler::test(
|
|
[](legacy_handler h)
|
|
{
|
|
return bind_front_handler(
|
|
h, error_code{}, std::size_t{});
|
|
});
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
template <class AsyncReadStream, class ReadHandler>
|
|
void
|
|
signal_aborted (AsyncReadStream& stream, ReadHandler&& handler)
|
|
{
|
|
net::post(
|
|
stream.get_executor(),
|
|
bind_handler (std::forward <ReadHandler> (handler),
|
|
net::error::operation_aborted, 0));
|
|
}
|
|
|
|
template <class AsyncReadStream, class ReadHandler>
|
|
void
|
|
signal_eof (AsyncReadStream& stream, ReadHandler&& handler)
|
|
{
|
|
net::post(
|
|
stream.get_executor(),
|
|
bind_front_handler (std::forward<ReadHandler> (handler),
|
|
net::error::eof, 0));
|
|
}
|
|
|
|
void
|
|
testJavadocs()
|
|
{
|
|
BEAST_EXPECT((
|
|
&bind_handler_test::signal_aborted<
|
|
test::stream, handler<error_code, std::size_t>>));
|
|
|
|
BEAST_EXPECT((
|
|
&bind_handler_test::signal_eof<
|
|
test::stream, handler<error_code, std::size_t>>));
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
|
|
void
|
|
run() override
|
|
{
|
|
testBindHandler();
|
|
testBindFrontHandler();
|
|
testJavadocs();
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE(beast,core,bind_handler);
|
|
|
|
} // beast
|
|
} // boost
|