[libcxx-commits] [libcxx] f599e7a - [libc++] Refactor __perfect_forward, bind_front and not_fn
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Mon Aug 9 12:32:38 PDT 2021
Author: Louis Dionne
Date: 2021-08-09T15:32:00-04:00
New Revision: f599e7a789cb3983c40541164dad64f62bf9d887
URL: https://github.com/llvm/llvm-project/commit/f599e7a789cb3983c40541164dad64f62bf9d887
DIFF: https://github.com/llvm/llvm-project/commit/f599e7a789cb3983c40541164dad64f62bf9d887.diff
LOG: [libc++] Refactor __perfect_forward, bind_front and not_fn
This patch fixes the constrains on the __perfect_forward constructor
and its call operators, which were incorrect. In particular, it makes
sure that we closely follow [func.require], which basically says that
we must deliver the bound arguments with the appropriate value category
or make the call ill-formed, but not silently fall back to using a
different value category.
As a fly-by, this patch also:
- Adds types __bind_front_t and __not_fn_t to make the result of
calling bind_front and not_fn more opaque, and improve diagnostics
for users.
- Adds a bunch of tests for bind_front and remove some that are now
redundant.
- Adds some missing _LIBCPP_HIDE_FROM_ABI annotations.
Immense thanks to @tcanens for raising awareness about this issue, and
providing help with the = delete bits.
Differential Revision: https://reviews.llvm.org/D107199
Added:
Modified:
libcxx/include/__functional/bind_front.h
libcxx/include/__functional/not_fn.h
libcxx/include/__functional/perfect_forward.h
libcxx/test/std/utilities/function.objects/func.bind_front/bind_front.pass.cpp
libcxx/test/std/utilities/function.objects/func.bind_front/bind_front.verify.cpp
Removed:
################################################################################
diff --git a/libcxx/include/__functional/bind_front.h b/libcxx/include/__functional/bind_front.h
index 8690499f2b0c9..331dec6779265 100644
--- a/libcxx/include/__functional/bind_front.h
+++ b/libcxx/include/__functional/bind_front.h
@@ -24,25 +24,31 @@ _LIBCPP_BEGIN_NAMESPACE_STD
#if _LIBCPP_STD_VER > 17
-struct __bind_front_op
-{
- template<class... _Args>
- constexpr static auto __call(_Args&&... __args)
- noexcept(noexcept(_VSTD::invoke(_VSTD::forward<_Args>(__args)...)))
- -> decltype( _VSTD::invoke(_VSTD::forward<_Args>(__args)...))
- { return _VSTD::invoke(_VSTD::forward<_Args>(__args)...); }
+struct __bind_front_op {
+ template <class ..._Args>
+ _LIBCPP_HIDE_FROM_ABI
+ constexpr auto operator()(_Args&& ...__args) const
+ noexcept(noexcept(_VSTD::invoke(_VSTD::forward<_Args>(__args)...)))
+ -> decltype( _VSTD::invoke(_VSTD::forward<_Args>(__args)...))
+ { return _VSTD::invoke(_VSTD::forward<_Args>(__args)...); }
};
-template<class _Fn, class... _Args,
- class = _EnableIf<conjunction<is_constructible<decay_t<_Fn>, _Fn>,
- is_move_constructible<decay_t<_Fn>>,
- is_constructible<decay_t<_Args>, _Args>...,
- is_move_constructible<decay_t<_Args>>...
- >::value>>
-constexpr auto bind_front(_Fn&& __f, _Args&&... __args)
-{
- return __perfect_forward<__bind_front_op, _Fn, _Args...>(_VSTD::forward<_Fn>(__f),
- _VSTD::forward<_Args>(__args)...);
+template <class _Fn, class ..._BoundArgs>
+struct __bind_front_t : __perfect_forward<__bind_front_op, _Fn, _BoundArgs...> {
+ using __perfect_forward<__bind_front_op, _Fn, _BoundArgs...>::__perfect_forward;
+};
+
+template <class _Fn, class... _Args, class = _EnableIf<
+ _And<
+ is_constructible<decay_t<_Fn>, _Fn>,
+ is_move_constructible<decay_t<_Fn>>,
+ is_constructible<decay_t<_Args>, _Args>...,
+ is_move_constructible<decay_t<_Args>>...
+ >::value
+>>
+_LIBCPP_HIDE_FROM_ABI
+constexpr auto bind_front(_Fn&& __f, _Args&&... __args) {
+ return __bind_front_t<decay_t<_Fn>, decay_t<_Args>...>(_VSTD::forward<_Fn>(__f), _VSTD::forward<_Args>(__args)...);
}
#endif // _LIBCPP_STD_VER > 17
diff --git a/libcxx/include/__functional/not_fn.h b/libcxx/include/__functional/not_fn.h
index 632be5ff096b5..98701fe6eb8c5 100644
--- a/libcxx/include/__functional/not_fn.h
+++ b/libcxx/include/__functional/not_fn.h
@@ -23,21 +23,27 @@ _LIBCPP_BEGIN_NAMESPACE_STD
#if _LIBCPP_STD_VER > 14
-struct __not_fn_op
-{
- template<class... _Args>
- static _LIBCPP_CONSTEXPR_AFTER_CXX17 auto __call(_Args&&... __args)
- noexcept(noexcept(!_VSTD::invoke(_VSTD::forward<_Args>(__args)...)))
- -> decltype( !_VSTD::invoke(_VSTD::forward<_Args>(__args)...))
- { return !_VSTD::invoke(_VSTD::forward<_Args>(__args)...); }
+struct __not_fn_op {
+ template <class... _Args>
+ _LIBCPP_HIDE_FROM_ABI
+ _LIBCPP_CONSTEXPR_AFTER_CXX17 auto operator()(_Args&&... __args) const
+ noexcept(noexcept(!_VSTD::invoke(_VSTD::forward<_Args>(__args)...)))
+ -> decltype( !_VSTD::invoke(_VSTD::forward<_Args>(__args)...))
+ { return !_VSTD::invoke(_VSTD::forward<_Args>(__args)...); }
};
-template<class _Fn,
- class = _EnableIf<is_constructible_v<decay_t<_Fn>, _Fn> &&
- is_move_constructible_v<_Fn>>>
-_LIBCPP_CONSTEXPR_AFTER_CXX17 auto not_fn(_Fn&& __f)
-{
- return __perfect_forward<__not_fn_op, _Fn>(_VSTD::forward<_Fn>(__f));
+template <class _Fn>
+struct __not_fn_t : __perfect_forward<__not_fn_op, _Fn> {
+ using __perfect_forward<__not_fn_op, _Fn>::__perfect_forward;
+};
+
+template <class _Fn, class = _EnableIf<
+ is_constructible_v<decay_t<_Fn>, _Fn> &&
+ is_move_constructible_v<decay_t<_Fn>>
+>>
+_LIBCPP_HIDE_FROM_ABI
+_LIBCPP_CONSTEXPR_AFTER_CXX17 auto not_fn(_Fn&& __f) {
+ return __not_fn_t<decay_t<_Fn>>(_VSTD::forward<_Fn>(__f));
}
#endif // _LIBCPP_STD_VER > 14
diff --git a/libcxx/include/__functional/perfect_forward.h b/libcxx/include/__functional/perfect_forward.h
index a5678e1593bba..9b456f0cc0f3d 100644
--- a/libcxx/include/__functional/perfect_forward.h
+++ b/libcxx/include/__functional/perfect_forward.h
@@ -11,9 +11,11 @@
#define _LIBCPP___FUNCTIONAL_PERFECT_FORWARD_H
#include <__config>
+#include <__utility/declval.h>
+#include <__utility/forward.h>
+#include <__utility/move.h>
#include <tuple>
#include <type_traits>
-#include <utility>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#pragma GCC system_header
@@ -23,63 +25,68 @@ _LIBCPP_BEGIN_NAMESPACE_STD
#if _LIBCPP_STD_VER > 14
-template<class _Op, class _Tuple,
- class _Idxs = typename __make_tuple_indices<tuple_size<_Tuple>::value>::type>
+template <class _Op, class _Indices, class ..._Bound>
struct __perfect_forward_impl;
-template<class _Op, class... _Bound, size_t... _Idxs>
-struct __perfect_forward_impl<_Op, __tuple_types<_Bound...>, __tuple_indices<_Idxs...>>
-{
+template <class _Op, size_t ..._Idx, class ..._Bound>
+struct __perfect_forward_impl<_Op, index_sequence<_Idx...>, _Bound...> {
+private:
tuple<_Bound...> __bound_;
- template<class... _Args>
- _LIBCPP_INLINE_VISIBILITY constexpr auto operator()(_Args&&... __args) &
- noexcept(noexcept(_Op::__call(_VSTD::get<_Idxs>(__bound_)..., _VSTD::forward<_Args>(__args)...)))
- -> decltype( _Op::__call(_VSTD::get<_Idxs>(__bound_)..., _VSTD::forward<_Args>(__args)...))
- {return _Op::__call(_VSTD::get<_Idxs>(__bound_)..., _VSTD::forward<_Args>(__args)...);}
-
- template<class... _Args>
- _LIBCPP_INLINE_VISIBILITY constexpr auto operator()(_Args&&... __args) const&
- noexcept(noexcept(_Op::__call(_VSTD::get<_Idxs>(__bound_)..., _VSTD::forward<_Args>(__args)...)))
- -> decltype( _Op::__call(_VSTD::get<_Idxs>(__bound_)..., _VSTD::forward<_Args>(__args)...))
- {return _Op::__call(_VSTD::get<_Idxs>(__bound_)..., _VSTD::forward<_Args>(__args)...);}
-
- template<class... _Args>
- _LIBCPP_INLINE_VISIBILITY constexpr auto operator()(_Args&&... __args) &&
- noexcept(noexcept(_Op::__call(_VSTD::get<_Idxs>(_VSTD::move(__bound_))...,
- _VSTD::forward<_Args>(__args)...)))
- -> decltype( _Op::__call(_VSTD::get<_Idxs>(_VSTD::move(__bound_))...,
- _VSTD::forward<_Args>(__args)...))
- {return _Op::__call(_VSTD::get<_Idxs>(_VSTD::move(__bound_))...,
- _VSTD::forward<_Args>(__args)...);}
-
- template<class... _Args>
- _LIBCPP_INLINE_VISIBILITY constexpr auto operator()(_Args&&... __args) const&&
- noexcept(noexcept(_Op::__call(_VSTD::get<_Idxs>(_VSTD::move(__bound_))...,
- _VSTD::forward<_Args>(__args)...)))
- -> decltype( _Op::__call(_VSTD::get<_Idxs>(_VSTD::move(__bound_))...,
- _VSTD::forward<_Args>(__args)...))
- {return _Op::__call(_VSTD::get<_Idxs>(_VSTD::move(__bound_))...,
- _VSTD::forward<_Args>(__args)...);}
-
- template<class _Fn = typename tuple_element<0, tuple<_Bound...>>::type,
- class = _EnableIf<is_copy_constructible_v<_Fn>>>
- constexpr __perfect_forward_impl(__perfect_forward_impl const& __other)
- : __bound_(__other.__bound_) {}
-
- template<class _Fn = typename tuple_element<0, tuple<_Bound...>>::type,
- class = _EnableIf<is_move_constructible_v<_Fn>>>
- constexpr __perfect_forward_impl(__perfect_forward_impl && __other)
- : __bound_(_VSTD::move(__other.__bound_)) {}
-
- template<class... _BoundArgs>
- explicit constexpr __perfect_forward_impl(_BoundArgs&&... __bound) :
- __bound_(_VSTD::forward<_BoundArgs>(__bound)...) { }
+public:
+ template <class ..._BoundArgs, class = _EnableIf<
+ is_constructible_v<tuple<_Bound...>, _BoundArgs&&...>
+ >>
+ explicit constexpr __perfect_forward_impl(_BoundArgs&& ...__bound)
+ : __bound_(_VSTD::forward<_BoundArgs>(__bound)...)
+ { }
+
+ __perfect_forward_impl(__perfect_forward_impl const&) = default;
+ __perfect_forward_impl(__perfect_forward_impl&&) = default;
+
+ __perfect_forward_impl& operator=(__perfect_forward_impl const&) = default;
+ __perfect_forward_impl& operator=(__perfect_forward_impl&&) = default;
+
+ template <class ..._Args, class = _EnableIf<is_invocable_v<_Op, _Bound&..., _Args...>>>
+ _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Args&&... __args) &
+ noexcept(noexcept(_Op()(_VSTD::get<_Idx>(__bound_)..., _VSTD::forward<_Args>(__args)...)))
+ -> decltype( _Op()(_VSTD::get<_Idx>(__bound_)..., _VSTD::forward<_Args>(__args)...))
+ { return _Op()(_VSTD::get<_Idx>(__bound_)..., _VSTD::forward<_Args>(__args)...); }
+
+ template <class ..._Args, class = _EnableIf<!is_invocable_v<_Op, _Bound&..., _Args...>>>
+ auto operator()(_Args&&...) & = delete;
+
+ template <class ..._Args, class = _EnableIf<is_invocable_v<_Op, _Bound const&..., _Args...>>>
+ _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Args&&... __args) const&
+ noexcept(noexcept(_Op()(_VSTD::get<_Idx>(__bound_)..., _VSTD::forward<_Args>(__args)...)))
+ -> decltype( _Op()(_VSTD::get<_Idx>(__bound_)..., _VSTD::forward<_Args>(__args)...))
+ { return _Op()(_VSTD::get<_Idx>(__bound_)..., _VSTD::forward<_Args>(__args)...); }
+
+ template <class ..._Args, class = _EnableIf<!is_invocable_v<_Op, _Bound const&..., _Args...>>>
+ auto operator()(_Args&&...) const& = delete;
+
+ template <class ..._Args, class = _EnableIf<is_invocable_v<_Op, _Bound..., _Args...>>>
+ _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Args&&... __args) &&
+ noexcept(noexcept(_Op()(_VSTD::get<_Idx>(_VSTD::move(__bound_))..., _VSTD::forward<_Args>(__args)...)))
+ -> decltype( _Op()(_VSTD::get<_Idx>(_VSTD::move(__bound_))..., _VSTD::forward<_Args>(__args)...))
+ { return _Op()(_VSTD::get<_Idx>(_VSTD::move(__bound_))..., _VSTD::forward<_Args>(__args)...); }
+
+ template <class ..._Args, class = _EnableIf<!is_invocable_v<_Op, _Bound..., _Args...>>>
+ auto operator()(_Args&&...) && = delete;
+
+ template <class ..._Args, class = _EnableIf<is_invocable_v<_Op, _Bound const..., _Args...>>>
+ _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Args&&... __args) const&&
+ noexcept(noexcept(_Op()(_VSTD::get<_Idx>(_VSTD::move(__bound_))..., _VSTD::forward<_Args>(__args)...)))
+ -> decltype( _Op()(_VSTD::get<_Idx>(_VSTD::move(__bound_))..., _VSTD::forward<_Args>(__args)...))
+ { return _Op()(_VSTD::get<_Idx>(_VSTD::move(__bound_))..., _VSTD::forward<_Args>(__args)...); }
+
+ template <class ..._Args, class = _EnableIf<!is_invocable_v<_Op, _Bound const..., _Args...>>>
+ auto operator()(_Args&&...) const&& = delete;
};
-template<class _Op, class... _Args>
-using __perfect_forward =
- __perfect_forward_impl<_Op, __tuple_types<decay_t<_Args>...>>;
+// __perfect_forward implements a perfect-forwarding call wrapper as explained in [func.require].
+template <class _Op, class ..._Args>
+using __perfect_forward = __perfect_forward_impl<_Op, index_sequence_for<_Args...>, _Args...>;
#endif // _LIBCPP_STD_VER > 14
diff --git a/libcxx/test/std/utilities/function.objects/func.bind_front/bind_front.pass.cpp b/libcxx/test/std/utilities/function.objects/func.bind_front/bind_front.pass.cpp
index e96357cb18f21..42127882a7ab3 100644
--- a/libcxx/test/std/utilities/function.objects/func.bind_front/bind_front.pass.cpp
+++ b/libcxx/test/std/utilities/function.objects/func.bind_front/bind_front.pass.cpp
@@ -10,32 +10,18 @@
// functional
-// template <class F, class... Args> constexpr unspecified bind_front(F&&, Args&&...);
+// template <class F, class... Args>
+// constexpr unspecified bind_front(F&&, Args&&...);
#include <functional>
+#include <cassert>
+#include <tuple>
+#include <type_traits>
+#include <utility>
#include "callable_types.h"
#include "test_macros.h"
-constexpr int add(int a, int b) { return a + b; }
-
-constexpr int long_test(int a, int b, int c, int d, int e, int f) {
- return a + b + c + d + e + f;
-}
-
-struct Foo {
- int a;
- int b;
-};
-
-struct FooCall {
- constexpr Foo operator()(int a, int b) { return Foo{a, b}; }
-};
-
-struct S {
- constexpr bool operator()(int a) { return a == 1; }
-};
-
struct CopyMoveInfo {
enum { none, copy, move } copy_kind;
@@ -44,252 +30,376 @@ struct CopyMoveInfo {
constexpr CopyMoveInfo(CopyMoveInfo&&) : copy_kind(move) {}
};
-constexpr bool wasCopied(CopyMoveInfo info) {
- return info.copy_kind == CopyMoveInfo::copy;
-}
-constexpr bool wasMoved(CopyMoveInfo info) {
- return info.copy_kind == CopyMoveInfo::move;
-}
-
-constexpr void basic_tests() {
- int n = 2;
- int m = 1;
-
- auto a = std::bind_front(add, m, n);
- assert(a() == 3);
-
- auto b = std::bind_front(long_test, m, n, m, m, m, m);
- assert(b() == 7);
-
- auto c = std::bind_front(long_test, n, m);
- assert(c(1, 1, 1, 1) == 7);
-
- auto d = std::bind_front(S{}, m);
- assert(d());
-
- auto f = std::bind_front(add, n);
- assert(f(3) == 5);
+template <class ...Args>
+struct is_bind_frontable {
+ template <class ...LocalArgs>
+ static auto test(int)
+ -> decltype((void)std::bind_front(std::declval<LocalArgs>()...), std::true_type());
- auto g = std::bind_front(add, n, 1);
- assert(g() == 3);
+ template <class...>
+ static std::false_type test(...);
- auto h = std::bind_front(long_test, 1, 1, 1);
- assert(h(2, 2, 2) == 9);
+ static constexpr bool value = decltype(test<Args...>(0))::value;
+};
- // Make sure the arg is passed by value.
- auto i = std::bind_front(add, n, 1);
- n = 100;
- assert(i() == 3);
+struct NotCopyMove {
+ NotCopyMove() = delete;
+ NotCopyMove(const NotCopyMove&) = delete;
+ NotCopyMove(NotCopyMove&&) = delete;
+ template <class ...Args>
+ void operator()(Args&& ...) const { }
+};
- CopyMoveInfo info;
- auto copied = std::bind_front(wasCopied, info);
- assert(copied());
+struct NonConstCopyConstructible {
+ explicit NonConstCopyConstructible() {}
+ NonConstCopyConstructible(NonConstCopyConstructible&) {}
+};
- auto moved = std::bind_front(wasMoved, info);
- assert(std::move(moved)());
-}
+struct MoveConstructible {
+ explicit MoveConstructible() {}
+ MoveConstructible(MoveConstructible&&) {}
+};
-struct variadic_fn {
- template <class... Args>
- constexpr int operator()(Args&&... args) {
- return sizeof...(args);
+struct MakeTuple {
+ template <class ...Args>
+ constexpr auto operator()(Args&& ...args) const {
+ return std::make_tuple(std::forward<Args>(args)...);
}
};
-constexpr void test_variadic() {
- variadic_fn value;
- auto fn = std::bind_front(value, 0, 0, 0);
- assert(fn(0, 0, 0) == 6);
-}
+template <int X>
+struct Elem {
+ template <int Y>
+ constexpr bool operator==(Elem<Y> const&) const
+ { return X == Y; }
+};
-struct mutable_callable {
- bool should_call_const;
+constexpr bool test() {
+ // Bind arguments, call without arguments
+ {
+ {
+ auto f = std::bind_front(MakeTuple{});
+ assert(f() == std::make_tuple());
+ }
+ {
+ auto f = std::bind_front(MakeTuple{}, Elem<1>{});
+ assert(f() == std::make_tuple(Elem<1>{}));
+ }
+ {
+ auto f = std::bind_front(MakeTuple{}, Elem<1>{}, Elem<2>{});
+ assert(f() == std::make_tuple(Elem<1>{}, Elem<2>{}));
+ }
+ {
+ auto f = std::bind_front(MakeTuple{}, Elem<1>{}, Elem<2>{}, Elem<3>{});
+ assert(f() == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<3>{}));
+ }
+ }
- constexpr bool operator()(int, int) {
- assert(!should_call_const);
- return true;
+ // Bind no arguments, call with arguments
+ {
+ {
+ auto f = std::bind_front(MakeTuple{});
+ assert(f(Elem<1>{}) == std::make_tuple(Elem<1>{}));
+ }
+ {
+ auto f = std::bind_front(MakeTuple{});
+ assert(f(Elem<1>{}, Elem<2>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}));
+ }
+ {
+ auto f = std::bind_front(MakeTuple{});
+ assert(f(Elem<1>{}, Elem<2>{}, Elem<3>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<3>{}));
+ }
}
- constexpr bool operator()(int, int) const {
- assert(should_call_const);
- return true;
+
+ // Bind arguments, call with arguments
+ {
+ {
+ auto f = std::bind_front(MakeTuple{}, Elem<1>{});
+ assert(f(Elem<10>{}) == std::make_tuple(Elem<1>{}, Elem<10>{}));
+ }
+ {
+ auto f = std::bind_front(MakeTuple{}, Elem<1>{}, Elem<2>{});
+ assert(f(Elem<10>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<10>{}));
+ }
+ {
+ auto f = std::bind_front(MakeTuple{}, Elem<1>{}, Elem<2>{}, Elem<3>{});
+ assert(f(Elem<10>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<3>{}, Elem<10>{}));
+ }
+
+ {
+ auto f = std::bind_front(MakeTuple{}, Elem<1>{});
+ assert(f(Elem<10>{}, Elem<11>{}) == std::make_tuple(Elem<1>{}, Elem<10>{}, Elem<11>{}));
+ }
+ {
+ auto f = std::bind_front(MakeTuple{}, Elem<1>{}, Elem<2>{});
+ assert(f(Elem<10>{}, Elem<11>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<10>{}, Elem<11>{}));
+ }
+ {
+ auto f = std::bind_front(MakeTuple{}, Elem<1>{}, Elem<2>{}, Elem<3>{});
+ assert(f(Elem<10>{}, Elem<11>{}) == std::make_tuple(Elem<1>{}, Elem<2>{}, Elem<3>{}, Elem<10>{}, Elem<11>{}));
+ }
}
-};
-constexpr void test_mutable() {
- const mutable_callable v1{true};
- const auto fn1 = std::bind_front(v1, 0);
- assert(fn1(0));
+ // Basic tests with fundamental types
+ {
+ int n = 2;
+ int m = 1;
+ auto add = [](int x, int y) { return x + y; };
+ auto addN = [](int a, int b, int c, int d, int e, int f) {
+ return a + b + c + d + e + f;
+ };
- mutable_callable v2{false};
- auto fn2 = std::bind_front(v2, 0);
- assert(fn2(0));
-};
+ auto a = std::bind_front(add, m, n);
+ assert(a() == 3);
-struct call_member {
- constexpr bool member(int, int) { return true; }
-};
+ auto b = std::bind_front(addN, m, n, m, m, m, m);
+ assert(b() == 7);
-constexpr void test_call_member() {
- call_member value;
- auto fn = std::bind_front(&call_member::member, value, 0);
- assert(fn(0));
-}
+ auto c = std::bind_front(addN, n, m);
+ assert(c(1, 1, 1, 1) == 7);
-struct no_const_lvalue {
- constexpr void operator()(int) && {};
-};
+ auto f = std::bind_front(add, n);
+ assert(f(3) == 5);
-constexpr auto make_no_const_lvalue(int x) {
- // This is to test that bind_front works when something like the following would not:
- // return [nc = no_const_lvalue{}, x] { return nc(x); };
- // Above would not work because it would look for a () const & overload.
- return std::bind_front(no_const_lvalue{}, x);
-}
+ auto g = std::bind_front(add, n, 1);
+ assert(g() == 3);
-constexpr void test_no_const_lvalue() { make_no_const_lvalue(1)(); }
+ auto h = std::bind_front(addN, 1, 1, 1);
+ assert(h(2, 2, 2) == 9);
+ }
-constexpr void constructor_tests() {
+ // Make sure we don't treat std::reference_wrapper specially.
{
- MoveOnlyCallable value(true);
- using RetT = decltype(std::bind_front(std::move(value), 1));
-
- static_assert(std::is_move_constructible<RetT>::value);
- static_assert(!std::is_copy_constructible<RetT>::value);
- static_assert(!std::is_move_assignable<RetT>::value);
- static_assert(!std::is_copy_assignable<RetT>::value);
-
- auto ret = std::bind_front(std::move(value), 1);
- assert(ret());
- assert(ret(1, 2, 3));
-
- auto ret1 = std::move(ret);
- assert(!ret());
- assert(ret1());
- assert(ret1(1, 2, 3));
+ auto add = [](std::reference_wrapper<int> a, std::reference_wrapper<int> b) {
+ return a.get() + b.get();
+ };
+ int i = 1, j = 2;
+ auto f = std::bind_front(add, std::ref(i));
+ assert(f(std::ref(j)) == 3);
}
+
+ // Make sure we can call a function that's a pointer to a member function.
{
- CopyCallable value(true);
- using RetT = decltype(std::bind_front(value, 1));
-
- static_assert(std::is_move_constructible<RetT>::value);
- static_assert(std::is_copy_constructible<RetT>::value);
- static_assert(!std::is_move_assignable<RetT>::value);
- static_assert(!std::is_copy_assignable<RetT>::value);
-
- auto ret = std::bind_front(value, 1);
- assert(ret());
- assert(ret(1, 2, 3));
-
- auto ret1 = std::move(ret);
- assert(ret1());
- assert(ret1(1, 2, 3));
-
- auto ret2 = std::bind_front(std::move(value), 1);
- assert(!ret());
- assert(ret2());
- assert(ret2(1, 2, 3));
+ struct MemberFunction {
+ constexpr bool foo(int, int) { return true; }
+ };
+ MemberFunction value;
+ auto fn = std::bind_front(&MemberFunction::foo, value, 0);
+ assert(fn(0));
}
- {
- CopyAssignableWrapper value(true);
- using RetT = decltype(std::bind_front(value, 1));
- static_assert(std::is_move_constructible<RetT>::value);
- static_assert(std::is_copy_constructible<RetT>::value);
- static_assert(std::is_move_assignable<RetT>::value);
- static_assert(std::is_copy_assignable<RetT>::value);
- }
+ // Make sure that we copy the bound arguments into the unspecified-type.
{
- MoveAssignableWrapper value(true);
- using RetT = decltype(std::bind_front(std::move(value), 1));
-
- static_assert(std::is_move_constructible<RetT>::value);
- static_assert(!std::is_copy_constructible<RetT>::value);
- static_assert(std::is_move_assignable<RetT>::value);
- static_assert(!std::is_copy_assignable<RetT>::value);
+ auto add = [](int x, int y) { return x + y; };
+ int n = 2;
+ auto i = std::bind_front(add, n, 1);
+ n = 100;
+ assert(i() == 3);
}
-}
-
-template <class Res, class F, class... Args>
-constexpr void test_return(F&& value, Args&&... args) {
- auto ret =
- std::bind_front(std::forward<F>(value), std::forward<Args>(args)...);
- static_assert(std::is_same<decltype(ret()), Res>::value);
-}
-constexpr void test_return_types() {
- test_return<Foo>(FooCall{}, 1, 2);
- test_return<bool>(S{}, 1);
- test_return<int>(add, 2, 2);
-}
-
-constexpr void test_arg_count() {
- using T = decltype(std::bind_front(add, 1));
- static_assert(!std::is_invocable<T>::value);
- static_assert(std::is_invocable<T, int>::value);
-}
-
-template <class... Args>
-struct is_bind_frontable {
- template <class... LocalArgs>
- static auto test(int)
- -> decltype((void)std::bind_front(std::declval<LocalArgs>()...),
- std::true_type());
-
- template <class...>
- static std::false_type test(...);
-
- static constexpr bool value = decltype(test<Args...>(0))::value;
-};
+ // Make sure we pass the bound arguments to the function object
+ // with the right value category.
+ {
+ {
+ auto wasCopied = [](CopyMoveInfo info) {
+ return info.copy_kind == CopyMoveInfo::copy;
+ };
+ CopyMoveInfo info;
+ auto copied = std::bind_front(wasCopied, info);
+ assert(copied());
+ }
+
+ {
+ auto wasMoved = [](CopyMoveInfo info) {
+ return info.copy_kind == CopyMoveInfo::move;
+ };
+ CopyMoveInfo info;
+ auto moved = std::bind_front(wasMoved, info);
+ assert(std::move(moved)());
+ }
+ }
-struct NotCopyMove {
- NotCopyMove() = delete;
- NotCopyMove(const NotCopyMove&) = delete;
- NotCopyMove(NotCopyMove&&) = delete;
- void operator()() {}
-};
+ // Make sure we call the correctly cv-ref qualified operator() based on the
+ // value category of the bind_front unspecified-type.
+ {
+ struct F {
+ constexpr int operator()() & { return 1; }
+ constexpr int operator()() const& { return 2; }
+ constexpr int operator()() && { return 3; }
+ constexpr int operator()() const&& { return 4; }
+ };
+ auto x = std::bind_front(F{});
+ using X = decltype(x);
+ assert(static_cast<X&>(x)() == 1);
+ assert(static_cast<X const&>(x)() == 2);
+ assert(static_cast<X&&>(x)() == 3);
+ assert(static_cast<X const&&>(x)() == 4);
+ }
-struct NonConstCopyConstructible {
- explicit NonConstCopyConstructible() {}
- NonConstCopyConstructible(NonConstCopyConstructible&) {}
-};
+ // Make sure the bind_front unspecified-type is NOT invocable when the call would select a
+ //
diff erently-qualified operator().
+ //
+ // For example, if the call to `operator()() &` is ill-formed, the call to the unspecified-type
+ // should be ill-formed and not fall back to the `operator()() const&` overload.
+ {
+ // Make sure we delete the & overload when the underlying call isn't valid
+ {
+ struct F {
+ void operator()() & = delete;
+ void operator()() const&;
+ void operator()() &&;
+ void operator()() const&&;
+ };
+ using X = decltype(std::bind_front(F{}));
+ static_assert(!std::is_invocable_v<X&>);
+ static_assert( std::is_invocable_v<X const&>);
+ static_assert( std::is_invocable_v<X>);
+ static_assert( std::is_invocable_v<X const>);
+ }
+
+ // There's no way to make sure we delete the const& overload when the underlying call isn't valid,
+ // so we can't check this one.
+
+ // Make sure we delete the && overload when the underlying call isn't valid
+ {
+ struct F {
+ void operator()() &;
+ void operator()() const&;
+ void operator()() && = delete;
+ void operator()() const&&;
+ };
+ using X = decltype(std::bind_front(F{}));
+ static_assert( std::is_invocable_v<X&>);
+ static_assert( std::is_invocable_v<X const&>);
+ static_assert(!std::is_invocable_v<X>);
+ static_assert( std::is_invocable_v<X const>);
+ }
+
+ // Make sure we delete the const&& overload when the underlying call isn't valid
+ {
+ struct F {
+ void operator()() &;
+ void operator()() const&;
+ void operator()() &&;
+ void operator()() const&& = delete;
+ };
+ using X = decltype(std::bind_front(F{}));
+ static_assert( std::is_invocable_v<X&>);
+ static_assert( std::is_invocable_v<X const&>);
+ static_assert( std::is_invocable_v<X>);
+ static_assert(!std::is_invocable_v<X const>);
+ }
+ }
-struct MoveConstructible {
- explicit MoveConstructible() {}
- MoveConstructible(MoveConstructible&&) {}
-};
+ // Some examples by Tim Song
+ {
+ {
+ struct T { };
+ struct F {
+ void operator()(T&&) const &;
+ void operator()(T&&) && = delete;
+ };
+ using X = decltype(std::bind_front(F{}));
+ static_assert(!std::is_invocable_v<X, T>);
+ }
+
+ {
+ struct T { };
+ struct F {
+ void operator()(T const&) const;
+ void operator()(T&&) const = delete;
+ };
+ using X = decltype(std::bind_front(F{}, T{}));
+ static_assert(!std::is_invocable_v<X>);
+ }
+ }
-constexpr void test_invocability() {
- static_assert(!std::is_constructible_v<NotCopyMove, NotCopyMove>);
- static_assert(!std::is_move_constructible_v<NotCopyMove>);
- static_assert(!is_bind_frontable<NotCopyMove>::value);
- static_assert(!is_bind_frontable<NotCopyMove&>::value);
-
- static_assert(
- !std::is_constructible_v<MoveConstructible, MoveConstructible&>);
- static_assert(std::is_move_constructible_v<MoveConstructible>);
- static_assert(is_bind_frontable<variadic_fn, MoveConstructible>::value);
- static_assert(
- !is_bind_frontable<variadic_fn, MoveConstructible&>::value);
-
- static_assert(std::is_constructible_v<NonConstCopyConstructible,
- NonConstCopyConstructible&>);
- static_assert(!std::is_move_constructible_v<NonConstCopyConstructible>);
- static_assert(
- !is_bind_frontable<variadic_fn, NonConstCopyConstructible&>::value);
- static_assert(
- !is_bind_frontable<variadic_fn, NonConstCopyConstructible>::value);
-}
+ // Test properties of the constructor of the unspecified-type returned by bind_front.
+ {
+ {
+ MoveOnlyCallable value(true);
+ using RetT = decltype(std::bind_front(std::move(value), 1));
+
+ static_assert( std::is_move_constructible<RetT>::value);
+ static_assert(!std::is_copy_constructible<RetT>::value);
+ static_assert(!std::is_move_assignable<RetT>::value);
+ static_assert(!std::is_copy_assignable<RetT>::value);
+
+ auto ret = std::bind_front(std::move(value), 1);
+ assert(ret());
+ assert(ret(1, 2, 3));
+
+ auto ret1 = std::move(ret);
+ assert(!ret());
+ assert(ret1());
+ assert(ret1(1, 2, 3));
+ }
+ {
+ CopyCallable value(true);
+ using RetT = decltype(std::bind_front(value, 1));
+
+ static_assert( std::is_move_constructible<RetT>::value);
+ static_assert( std::is_copy_constructible<RetT>::value);
+ static_assert(!std::is_move_assignable<RetT>::value);
+ static_assert(!std::is_copy_assignable<RetT>::value);
+
+ auto ret = std::bind_front(value, 1);
+ assert(ret());
+ assert(ret(1, 2, 3));
+
+ auto ret1 = std::move(ret);
+ assert(ret1());
+ assert(ret1(1, 2, 3));
+
+ auto ret2 = std::bind_front(std::move(value), 1);
+ assert(!ret());
+ assert(ret2());
+ assert(ret2(1, 2, 3));
+ }
+ {
+ CopyAssignableWrapper value(true);
+ using RetT = decltype(std::bind_front(value, 1));
+
+ static_assert(std::is_move_constructible<RetT>::value);
+ static_assert(std::is_copy_constructible<RetT>::value);
+ static_assert(std::is_move_assignable<RetT>::value);
+ static_assert(std::is_copy_assignable<RetT>::value);
+ }
+ {
+ MoveAssignableWrapper value(true);
+ using RetT = decltype(std::bind_front(std::move(value), 1));
+
+ static_assert( std::is_move_constructible<RetT>::value);
+ static_assert(!std::is_copy_constructible<RetT>::value);
+ static_assert( std::is_move_assignable<RetT>::value);
+ static_assert(!std::is_copy_assignable<RetT>::value);
+ }
+ }
-constexpr bool test() {
- basic_tests();
- constructor_tests();
- test_return_types();
- test_arg_count();
- test_variadic();
- test_mutable();
- test_call_member();
- test_no_const_lvalue();
- test_invocability();
+ // Make sure bind_front is SFINAE friendly
+ {
+ using T = decltype(std::bind_front(std::declval<int(*)(int, int)>(), 1));
+ static_assert(!std::is_invocable<T>::value);
+ static_assert( std::is_invocable<T, int>::value);
+ static_assert(!std::is_invocable<T, void*>::value);
+ static_assert(!std::is_invocable<T, int, int>::value);
+
+ static_assert(!std::is_constructible_v<NotCopyMove, NotCopyMove&>);
+ static_assert(!std::is_move_constructible_v<NotCopyMove>);
+ static_assert(!is_bind_frontable<NotCopyMove>::value);
+ static_assert(!is_bind_frontable<NotCopyMove&>::value);
+
+ auto takeAnything = [](auto&& ...) { };
+ static_assert(!std::is_constructible_v<MoveConstructible, MoveConstructible&>);
+ static_assert( std::is_move_constructible_v<MoveConstructible>);
+ static_assert( is_bind_frontable<decltype(takeAnything), MoveConstructible>::value);
+ static_assert(!is_bind_frontable<decltype(takeAnything), MoveConstructible&>::value);
+
+ static_assert( std::is_constructible_v<NonConstCopyConstructible, NonConstCopyConstructible&>);
+ static_assert(!std::is_move_constructible_v<NonConstCopyConstructible>);
+ static_assert(!is_bind_frontable<decltype(takeAnything), NonConstCopyConstructible&>::value);
+ static_assert(!is_bind_frontable<decltype(takeAnything), NonConstCopyConstructible>::value);
+ }
return true;
}
diff --git a/libcxx/test/std/utilities/function.objects/func.bind_front/bind_front.verify.cpp b/libcxx/test/std/utilities/function.objects/func.bind_front/bind_front.verify.cpp
index d8e7c4ce39aa5..729045961e1c4 100644
--- a/libcxx/test/std/utilities/function.objects/func.bind_front/bind_front.verify.cpp
+++ b/libcxx/test/std/utilities/function.objects/func.bind_front/bind_front.verify.cpp
@@ -10,7 +10,8 @@
// functional
-// template <class F, class... Args> constexpr unspecified bind_front(F&&, Args&&...);
+// template <class F, class... Args>
+// constexpr unspecified bind_front(F&&, Args&&...);
#include <functional>
More information about the libcxx-commits
mailing list