[libc++] Refactor __perfect_forward, bind_front and not_fn
authorLouis Dionne <ldionne.2@gmail.com>
Fri, 30 Jul 2021 18:38:14 +0000 (14:38 -0400)
committerLouis Dionne <ldionne.2@gmail.com>
Mon, 9 Aug 2021 19:32:00 +0000 (15:32 -0400)
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

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

index 8690499..331dec6 100644 (file)
@@ -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
index 632be5f..98701fe 100644 (file)
@@ -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
index a5678e1..9b456f0 100644 (file)
 #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
 
index e96357c..4212788 100644 (file)
 
 // 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
+  // differently-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;
 }
index d8e7c4c..7290459 100644 (file)
@@ -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>