This patch fixes a missing forwarding-reference (&&) in _Bind_fn_t::operator()
and lambda returned from not_fn<f>.
The bind_front<f>/bind_back<f> tests were updated to use a structure similar
to r16-3398-g250dd5b5604fbc to cover cases involving zero, one, and many bound
arguments.
PR libstdc++/122022
libstdc++-v3/ChangeLog:
* include/std/functional (_Bind_fn_t): Use forwarding reference as
paremeter.
(std::not_fn<f>): Use forwarding reference as lambda parameter.
* testsuite/20_util/function_objects/bind_back/nttp.cc: Rework tests.
* testsuite/20_util/function_objects/bind_front/nttp.cc: Likewise.
* testsuite/20_util/function_objects/not_fn/nttp.cc: Add test for
argument forwarding.
---
v2 mentions the PR122022 and extend fix to not_fn.
libstdc++-v3/include/std/functional | 8 +-
.../function_objects/bind_back/nttp.cc | 244 +++++++++++------
.../function_objects/bind_front/nttp.cc | 248 +++++++++++-------
.../20_util/function_objects/not_fn/nttp.cc | 47 ++++
4 files changed, 366 insertions(+), 181 deletions(-)
diff --git a/libstdc++-v3/include/std/functional
b/libstdc++-v3/include/std/functional
index 8ad73b343bd..dd1aa204eae 100644
--- a/libstdc++-v3/include/std/functional
+++ b/libstdc++-v3/include/std/functional
@@ -928,10 +928,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
{
using _Fn = const decltype(__fn)&;
template <typename... _Args>
+ requires is_invocable_v<_Fn, _Args...>
constexpr static decltype(auto)
- operator()(_Args... __args)
- noexcept(is_nothrow_invocable_v<_Fn, _Args...>)
- requires is_invocable_v<_Fn, _Args...>
+ operator()(_Args&&... __args)
+ noexcept(is_nothrow_invocable_v<_Fn, _Args...>)
{ return std::invoke(__fn, std::forward<_Args>(__args)...); }
};
#endif
@@ -1188,7 +1188,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
using _Fn = decltype(__fn);
if constexpr (is_pointer_v<_Fn> || is_member_pointer_v<_Fn>)
static_assert(__fn != nullptr);
- return []<typename... _Args>(_Args... __args) static
+ return []<typename... _Args>(_Args&&... __args) static
noexcept(noexcept(
!std::invoke(__fn, std::forward<_Args>(__args)...) ))
-> decltype(auto)
diff --git a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
index 4dff909a387..3bea8eced43 100644
--- a/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
+++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_back/nttp.cc
@@ -39,101 +39,145 @@ test01()
>);
}
+struct quals
+{
+ bool as_const;
+ bool as_lvalue;
+};
+
+template<typename... Args>
void
-test02()
+testTarget(Args... args)
{
- struct quals
+ struct F
{
- bool as_const;
- bool as_lvalue;
+ quals operator()(Args...) & { return { false, true }; }
+ quals operator()(Args...) const & { return { true, true }; }
+ quals operator()(Args...) && { return { false, false }; }
+ quals operator()(Args...) const && { return { true, false }; }
};
+ constexpr F f;
+ auto g = bind_back<f>(args...);
+ const auto& cg = g;
+ quals q;
+
+ // template parameter object is always constant lvalue
+ q = g();
+ VERIFY( q.as_const && q.as_lvalue );
+ q = std::move(g)();
+ VERIFY( q.as_const && q.as_lvalue );
+ q = cg();
+ VERIFY( q.as_const && q.as_lvalue );
+ q = std::move(cg)();
+ VERIFY( q.as_const && q.as_lvalue );
+}
+
+template<typename... Args>
+void
+testBoundArgs(Args... args)
+{
struct F
{
- quals operator()(int, int) & { return { false, true }; }
- quals operator()(int, int) const & { return { true, true }; }
- quals operator()(int, int) && { return { false, false }; }
- quals operator()(int, int) const && { return { true, false }; }
+ quals operator()(Args..., int&) const { return { false, true }; }
+ quals operator()(Args..., int const&) const { return { true, true }; }
+ quals operator()(Args..., int&&) const { return { false, false }; }
+ quals operator()(Args..., int const&&) const { return { true, false }; }
};
- // Constness and value category forwarded to the target object?
- { // no bound args
- constexpr F f;
- auto g = bind_back<f>();
- const auto& cg = g;
- quals q;
-
- q = g(0,0);
- VERIFY( q.as_const && q.as_lvalue );
- q = std::move(g)(0,0);
- VERIFY( q.as_const && q.as_lvalue );
- q = cg(0,0);
- VERIFY( q.as_const && q.as_lvalue );
- q = std::move(cg)(0,0);
- VERIFY( q.as_const && q.as_lvalue );
- }
- { // one bound arg
- constexpr F f;
- auto g = bind_back<f>(0);
- const auto& cg = g;
- quals q;
-
- q = g(0);
- VERIFY( q.as_const && q.as_lvalue );
- q = std::move(g)(0);
- VERIFY( q.as_const && q.as_lvalue );
- q = cg(0);
- VERIFY( q.as_const && q.as_lvalue );
- q = std::move(cg)(0);
- VERIFY( q.as_const && q.as_lvalue );
- }
- { // two bound args, the general case
- constexpr F f;
- auto g = bind_back<f>(0,0);
- const auto& cg = g;
- quals q;
-
- q = g();
- VERIFY( q.as_const && q.as_lvalue );
- q = std::move(g)();
- VERIFY( q.as_const && q.as_lvalue );
- q = cg();
- VERIFY( q.as_const && q.as_lvalue );
- q = std::move(cg)();
- VERIFY( q.as_const && q.as_lvalue );
- }
+ constexpr F f;
+ auto g = bind_back<f>(args..., 10);
+ const auto& cg = g;
+ quals q;
+
+ // constness and value category should be forwarded to the bound objects:
+ q = g();
+ VERIFY( ! q.as_const && q.as_lvalue );
+ q = std::move(g)();
+ VERIFY( ! q.as_const && ! q.as_lvalue );
+ q = cg();
+ VERIFY( q.as_const && q.as_lvalue );
+ q = std::move(cg)();
+ VERIFY( q.as_const && ! q.as_lvalue );
+
+ int i = 0;
+ auto gr = bind_back<f>(args..., std::ref(i));
+ const auto& cgr = gr;
+
+ // bound object is reference wrapper, converts to same type of reference
+ q = gr();
+ VERIFY( ! q.as_const && q.as_lvalue );
+ q = std::move(gr)();
+ VERIFY( ! q.as_const && q.as_lvalue );
+ q = cgr();
+ VERIFY( ! q.as_const && q.as_lvalue );
+ q = std::move(cgr)();
+ VERIFY( ! q.as_const && q.as_lvalue );
+
+ auto gcr = bind_back<f>(args..., std::cref(i));
+ const auto& cgcr = gcr;
+
+ q = gcr();
+ VERIFY( q.as_const && q.as_lvalue );
+ q = std::move(gcr)();
+ VERIFY( q.as_const && q.as_lvalue );
+ q = cgcr();
+ VERIFY( q.as_const && q.as_lvalue );
+ q = std::move(cgcr)();
+ VERIFY( q.as_const && q.as_lvalue );
}
+template<typename... Args>
void
-test02a()
+testCallArgs(Args... args)
{
- struct quals
- {
- bool as_const;
- bool as_lvalue;
- };
struct F
{
- quals operator()(int, int&) const { return { false, true }; }
- quals operator()(int, int const&) const { return { true, true }; }
- quals operator()(int, int&&) const { return { false, false }; }
- quals operator()(int, int const&&) const { return { true, false }; }
+ quals operator()(int&, Args...) const { return { false, true }; }
+ quals operator()(int const&, Args...) const { return { true, true }; }
+ quals operator()(int&&, Args...) const { return { false, false }; }
+ quals operator()(int const&&, Args...) const { return { true, false }; }
};
- constexpr F f{};
- // verify propagation
- auto h = bind_back<f>(10);
- auto const& ch = h;
+ constexpr F f;
+ auto g = bind_back<f>(args...);
+ const auto& cg = g;
quals q;
-
- q = h(0);
- VERIFY( !q.as_const && q.as_lvalue );
- q = ch(0);
+ int i = 10;
+ const int ci = i;
+
+ q = g(i);
+ VERIFY( ! q.as_const && q.as_lvalue );
+ q = g(std::move(i));
+ VERIFY( ! q.as_const && ! q.as_lvalue );
+ q = g(ci);
+ VERIFY( q.as_const && q.as_lvalue );
+ q = g(std::move(ci));
+ VERIFY( q.as_const && ! q.as_lvalue );
+
+ q = cg(i);
+ VERIFY( ! q.as_const && q.as_lvalue );
+ q = cg(std::move(i));
+ VERIFY( ! q.as_const && ! q.as_lvalue );
+ q = cg(ci);
VERIFY( q.as_const && q.as_lvalue );
- q = std::move(h)(0);
- VERIFY( !q.as_const && !q.as_lvalue );
- q = std::move(ch)(0);
- VERIFY( q.as_const && !q.as_lvalue );
+ q = cg(std::move(ci));
+ VERIFY( q.as_const && ! q.as_lvalue );
+
+ struct S
+ {
+ int operator()(long, long, Args...) const { return 1; }
+ int operator()(int, void*, Args...) const { return 2; }
+ };
+
+ constexpr S s;
+ // literal zero can be converted to any pointer, so (int, void*)
+ // is best candidate
+ VERIFY( s(0, 0, args...) == 2 );
+ // both arguments are bound to int&&, and no longer can be
+ // converted to pointer, (long, long) is only candidate
+ VERIFY( bind_back<s>()(0, 0, args...) == 1 );
+ VERIFY( bind_back<s>(args...)(0, 0) == 1 );
}
void
@@ -233,26 +277,52 @@ test04()
return true;
}
-struct C { int i = 0; };
-struct D : C { D(){} D(D&&) { ++i; } };
-int f5(D const& d1, D const& d2, D const& d3)
-{ return d1.i + d2.i + d3.i; }
+struct CountedArg
+{
+ CountedArg() = default;
+ CountedArg(CountedArg&& f) noexcept : counter(f.counter) { ++counter; }
+ CountedArg& operator=(CountedArg&&) = delete;
+
+ int counter = 0;
+};
+CountedArg const c;
-void test05()
+void
+testMaterialization()
{
- // Must move arguments into capture object, not construct in place
- // like normal arguments.
- VERIFY( bind_back<f5>(D{}, D{})(D{}) == 2 );
+ struct F
+ {
+ int operator()(CountedArg arg, int) const
+ { return arg.counter; };
+ };
+
+ // CountedArg is bound to rvalue-reference thus moved
+ auto f0 = std::bind_back<F{}>();
+ VERIFY( f0(CountedArg(), 10) == 1 );
+
+ auto f1 = std::bind_back<F{}>(10);
+ VERIFY( f1(CountedArg()) == 1 );
}
int
main()
{
test01();
- test02();
- test02a();
test03();
test03a();
static_assert(test04());
- test05();
+
+ testTarget();
+ testTarget(10);
+ testTarget(10, 20, 30);
+
+ testBoundArgs();
+ testBoundArgs(10);
+ testBoundArgs(10, 20, 30);
+
+ testCallArgs();
+ testCallArgs(10);
+ testCallArgs(10, 20, 30);
+
+ testMaterialization();
}
diff --git a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
index 0839c849c0c..9eb3c432a86 100644
--- a/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
+++ b/libstdc++-v3/testsuite/20_util/function_objects/bind_front/nttp.cc
@@ -39,104 +39,145 @@ test01()
>);
}
-void
-test02()
+struct quals
{
- struct quals
- {
- bool as_const;
- bool as_lvalue;
- };
+ bool as_const;
+ bool as_lvalue;
+};
+template<typename... Args>
+void
+testTarget(Args... args)
+{
struct F
{
- quals operator()(int, int) & { return { false, true }; }
- quals operator()(int, int) const & { return { true, true }; }
- quals operator()(int, int) && { return { false, false }; }
- quals operator()(int, int) const && { return { true, false }; }
+ quals operator()(Args...) & { return { false, true }; }
+ quals operator()(Args...) const & { return { true, true }; }
+ quals operator()(Args...) && { return { false, false }; }
+ quals operator()(Args...) const && { return { true, false }; }
};
- // Constness and value category forwarded to the target object?
- { // no bound args
- constexpr F f;
- auto g = bind_front<f>();
- const auto& cg = g;
- quals q;
-
- // Constness and value category forwarded to the target object?
- q = g(0,0);
- VERIFY( q.as_const && q.as_lvalue );
- q = std::move(g)(0,0);
- VERIFY( q.as_const && q.as_lvalue );
- q = cg(0,0);
- VERIFY( q.as_const && q.as_lvalue );
- q = std::move(cg)(0,0);
- VERIFY( q.as_const && q.as_lvalue );
- }
- { // one bound arg (for when we implement that as a separate case)
- constexpr F f;
- auto g = bind_front<f>(0);
- const auto& cg = g;
- quals q;
-
- // Constness and value category forwarded to the target object?
- q = g(0);
- VERIFY( q.as_const && q.as_lvalue );
- q = std::move(g)(0);
- VERIFY( q.as_const && q.as_lvalue );
- q = cg(0);
- VERIFY( q.as_const && q.as_lvalue );
- q = std::move(cg)(0);
- VERIFY( q.as_const && q.as_lvalue );
- }
- { // two bound args, the general case
- constexpr F f;
- auto g = bind_front<f>(0,0);
- const auto& cg = g;
- quals q;
-
- q = g();
- VERIFY( q.as_const && q.as_lvalue );
- q = std::move(g)();
- VERIFY( q.as_const && q.as_lvalue );
- q = cg();
- VERIFY( q.as_const && q.as_lvalue );
- q = std::move(cg)();
- VERIFY( q.as_const && q.as_lvalue );
- }
+ constexpr F f;
+ auto g = bind_front<f>(args...);
+ const auto& cg = g;
+ quals q;
+
+ // template parameter object is always constant lvalue
+ q = g();
+ VERIFY( q.as_const && q.as_lvalue );
+ q = std::move(g)();
+ VERIFY( q.as_const && q.as_lvalue );
+ q = cg();
+ VERIFY( q.as_const && q.as_lvalue );
+ q = std::move(cg)();
+ VERIFY( q.as_const && q.as_lvalue );
}
+template<typename... Args>
void
-test02a()
+testBoundArgs(Args... args)
{
- struct quals
+ struct F
{
- bool as_const;
- bool as_lvalue;
+ quals operator()(Args..., int&) const { return { false, true }; }
+ quals operator()(Args..., int const&) const { return { true, true }; }
+ quals operator()(Args..., int&&) const { return { false, false }; }
+ quals operator()(Args..., int const&&) const { return { true, false }; }
};
+ constexpr F f;
+ auto g = bind_front<f>(args..., 10);
+ const auto& cg = g;
+ quals q;
+
+ // constness and value category should be forwarded to the bound objects:
+ q = g();
+ VERIFY( ! q.as_const && q.as_lvalue );
+ q = std::move(g)();
+ VERIFY( ! q.as_const && ! q.as_lvalue );
+ q = cg();
+ VERIFY( q.as_const && q.as_lvalue );
+ q = std::move(cg)();
+ VERIFY( q.as_const && ! q.as_lvalue );
+
+ int i = 0;
+ auto gr = bind_front<f>(args..., std::ref(i));
+ const auto& cgr = gr;
+
+ // bound object is reference wrapper, converts to same type of reference
+ q = gr();
+ VERIFY( ! q.as_const && q.as_lvalue );
+ q = std::move(gr)();
+ VERIFY( ! q.as_const && q.as_lvalue );
+ q = cgr();
+ VERIFY( ! q.as_const && q.as_lvalue );
+ q = std::move(cgr)();
+ VERIFY( ! q.as_const && q.as_lvalue );
+
+ auto gcr = bind_front<f>(args..., std::cref(i));
+ const auto& cgcr = gcr;
+
+ q = gcr();
+ VERIFY( q.as_const && q.as_lvalue );
+ q = std::move(gcr)();
+ VERIFY( q.as_const && q.as_lvalue );
+ q = cgcr();
+ VERIFY( q.as_const && q.as_lvalue );
+ q = std::move(cgcr)();
+ VERIFY( q.as_const && q.as_lvalue );
+}
+
+template<typename... Args>
+void
+testCallArgs(Args... args)
+{
struct F
{
- quals operator()(int&, int) const { return { false, true }; }
- quals operator()(int const&, int) const { return { true, true }; }
- quals operator()(int&&, int) const { return { false, false }; }
- quals operator()(int const&&, int) const { return { true, false }; }
+ quals operator()(Args..., int&) const { return { false, true }; }
+ quals operator()(Args..., int const&) const { return { true, true }; }
+ quals operator()(Args..., int&&) const { return { false, false }; }
+ quals operator()(Args..., int const&&) const { return { true, false }; }
};
- constexpr F f{};
- // verify propagation
- auto h = bind_front<f>(10);
- auto const& ch = h;
+ constexpr F f;
+ auto g = bind_front<f>(args...);
+ const auto& cg = g;
quals q;
-
- q = h(0);
- VERIFY( !q.as_const && q.as_lvalue );
- q = ch(0);
+ int i = 10;
+ const int ci = i;
+
+ q = g(i);
+ VERIFY( ! q.as_const && q.as_lvalue );
+ q = g(std::move(i));
+ VERIFY( ! q.as_const && ! q.as_lvalue );
+ q = g(ci);
+ VERIFY( q.as_const && q.as_lvalue );
+ q = g(std::move(ci));
+ VERIFY( q.as_const && ! q.as_lvalue );
+
+ q = cg(i);
+ VERIFY( ! q.as_const && q.as_lvalue );
+ q = cg(std::move(i));
+ VERIFY( ! q.as_const && ! q.as_lvalue );
+ q = cg(ci);
VERIFY( q.as_const && q.as_lvalue );
- q = std::move(h)(0);
- VERIFY( !q.as_const && !q.as_lvalue );
- q = std::move(ch)(0);
- VERIFY( q.as_const && !q.as_lvalue );
+ q = cg(std::move(ci));
+ VERIFY( q.as_const && ! q.as_lvalue );
+
+ struct S
+ {
+ int operator()(Args..., long, long) const { return 1; }
+ int operator()(Args..., int, void*) const { return 2; }
+ };
+
+ constexpr S s;
+ // literal zero can be converted to any pointer, so (int, void*)
+ // is best candidate
+ VERIFY( s(args..., 0, 0) == 2 );
+ // both arguments are bound to int&&, and no longer can be
+ // converted to pointer, (long, long) is only candidate
+ VERIFY( bind_front<s>()(args..., 0, 0) == 1 );
+ VERIFY( bind_front<s>(args...)(0, 0) == 1 );
}
void
@@ -235,26 +276,53 @@ test04()
return true;
}
-struct C { int i = 0; };
-struct D : C { D(){} D(D&&) { ++i; } };
-int f5(D const& d1, D const& d2, D const& d3)
-{ return d1.i + d2.i + d3.i; }
+struct CountedArg
+{
+ CountedArg() = default;
+ CountedArg(CountedArg&& f) noexcept : counter(f.counter) { ++counter; }
+ CountedArg& operator=(CountedArg&&) = delete;
+
+ int counter = 0;
+};
+CountedArg const c;
-void test05()
+void
+testMaterialization()
{
- // Must move arguments into capture object, not construct in place
- // like normal arguments.
- VERIFY( bind_front<f5>(D{}, D{})(D{}) == 2 );
+ struct F
+ {
+ int operator()(int, CountedArg arg) const
+ { return arg.counter; };
+ };
+
+ // CountedArg is bound to rvalue-reference thus moved
+ auto f0 = std::bind_front<F{}>();
+ VERIFY( f0(10, CountedArg()) == 1 );
+
+ auto f1 = std::bind_front<F{}>(10);
+ VERIFY( f1(CountedArg()) == 1 );
}
int
main()
{
test01();
- test02();
- test02a();
test03();
test03a();
static_assert(test04());
- test05();
+
+ testTarget();
+ testTarget(10);
+ testTarget(10, 20, 30);
+
+ testBoundArgs();
+ testBoundArgs(10);
+ testBoundArgs(10, 20, 30);
+
+ testCallArgs();
+ testCallArgs(10);
+ testCallArgs(10, 20, 30);
+
+ testMaterialization();
+
}
diff --git a/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc
b/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc
index d24ccf8a187..3567d679a77 100644
--- a/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc
+++ b/libstdc++-v3/testsuite/20_util/function_objects/not_fn/nttp.cc
@@ -81,6 +81,52 @@ test07()
static_assert( !std::is_invocable<NotF>::value, "cannot negate" );
}
+void
+test08()
+{
+ struct quals
+ {
+ bool as_const;
+ bool as_lvalue;
+
+ quals operator!() const
+ { return *this; };
+ };
+
+ struct F
+ {
+ quals operator()(int&) const { return { false, true }; }
+ quals operator()(int const&) const { return { true, true }; }
+ quals operator()(int&&) const { return { false, false }; }
+ quals operator()(int const&&) const { return { true, false }; }
+ };
+
+ constexpr F f;
+ auto g = not_fn<f>();
+ const auto& cg = g;
+ quals q;
+ int i = 10;
+ const int ci = i;
+
+ q = g(i);
+ VERIFY( ! q.as_const && q.as_lvalue );
+ q = g(std::move(i));
+ VERIFY( ! q.as_const && ! q.as_lvalue );
+ q = g(ci);
+ VERIFY( q.as_const && q.as_lvalue );
+ q = g(std::move(ci));
+ VERIFY( q.as_const && ! q.as_lvalue );
+
+ q = cg(i);
+ VERIFY( ! q.as_const && q.as_lvalue );
+ q = cg(std::move(i));
+ VERIFY( ! q.as_const && ! q.as_lvalue );
+ q = cg(ci);
+ VERIFY( q.as_const && q.as_lvalue );
+ q = cg(std::move(ci));
+ VERIFY( q.as_const && ! q.as_lvalue );
+}
+
int
main()
{
@@ -89,6 +135,7 @@ main()
test05();
test06();
test07();
+ test08();
constexpr auto f = []{ return false; };
static_assert(std::not_fn<f>()());
}
--
2.51.0