[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
This commit is contained in:
Louis Dionne 2021-07-30 14:38:14 -04:00
parent d232ec3c2a
commit f599e7a789
5 changed files with 445 additions and 315 deletions

View 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

View 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

View File

@ -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)...);}
public:
template <class ..._BoundArgs, class = _EnableIf<
is_constructible_v<tuple<_Bound...>, _BoundArgs&&...>
>>
explicit constexpr __perfect_forward_impl(_BoundArgs&& ...__bound)
: __bound_(_VSTD::forward<_BoundArgs>(__bound)...)
{ }
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)...);}
__perfect_forward_impl(__perfect_forward_impl const&) = default;
__perfect_forward_impl(__perfect_forward_impl&&) = default;
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)...);}
__perfect_forward_impl& operator=(__perfect_forward_impl const&) = default;
__perfect_forward_impl& operator=(__perfect_forward_impl&&) = default;
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 ..._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 _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 ..._Args, class = _EnableIf<!is_invocable_v<_Op, _Bound&..., _Args...>>>
auto operator()(_Args&&...) & = delete;
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 ..._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... _BoundArgs>
explicit constexpr __perfect_forward_impl(_BoundArgs&&... __bound) :
__bound_(_VSTD::forward<_BoundArgs>(__bound)...) { }
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

View File

@ -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,196 +30,11 @@ 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);
auto g = std::bind_front(add, n, 1);
assert(g() == 3);
auto h = std::bind_front(long_test, 1, 1, 1);
assert(h(2, 2, 2) == 9);
// Make sure the arg is passed by value.
auto i = std::bind_front(add, n, 1);
n = 100;
assert(i() == 3);
CopyMoveInfo info;
auto copied = std::bind_front(wasCopied, info);
assert(copied());
auto moved = std::bind_front(wasMoved, info);
assert(std::move(moved)());
}
struct variadic_fn {
template <class... Args>
constexpr int operator()(Args&&... args) {
return sizeof...(args);
}
};
constexpr void test_variadic() {
variadic_fn value;
auto fn = std::bind_front(value, 0, 0, 0);
assert(fn(0, 0, 0) == 6);
}
struct mutable_callable {
bool should_call_const;
constexpr bool operator()(int, int) {
assert(!should_call_const);
return true;
}
constexpr bool operator()(int, int) const {
assert(should_call_const);
return true;
}
};
constexpr void test_mutable() {
const mutable_callable v1{true};
const auto fn1 = std::bind_front(v1, 0);
assert(fn1(0));
mutable_callable v2{false};
auto fn2 = std::bind_front(v2, 0);
assert(fn2(0));
};
struct call_member {
constexpr bool member(int, int) { return true; }
};
constexpr void test_call_member() {
call_member value;
auto fn = std::bind_front(&call_member::member, value, 0);
assert(fn(0));
}
struct no_const_lvalue {
constexpr void operator()(int) && {};
};
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);
}
constexpr void test_no_const_lvalue() { make_no_const_lvalue(1)(); }
constexpr void constructor_tests() {
{
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);
}
}
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>
template <class ...Args>
struct is_bind_frontable {
template <class... LocalArgs>
template <class ...LocalArgs>
static auto test(int)
-> decltype((void)std::bind_front(std::declval<LocalArgs>()...),
std::true_type());
-> decltype((void)std::bind_front(std::declval<LocalArgs>()...), std::true_type());
template <class...>
static std::false_type test(...);
@ -245,7 +46,8 @@ struct NotCopyMove {
NotCopyMove() = delete;
NotCopyMove(const NotCopyMove&) = delete;
NotCopyMove(NotCopyMove&&) = delete;
void operator()() {}
template <class ...Args>
void operator()(Args&& ...) const { }
};
struct NonConstCopyConstructible {
@ -258,38 +60,346 @@ struct MoveConstructible {
MoveConstructible(MoveConstructible&&) {}
};
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);
struct MakeTuple {
template <class ...Args>
constexpr auto operator()(Args&& ...args) const {
return std::make_tuple(std::forward<Args>(args)...);
}
};
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);
}
template <int X>
struct Elem {
template <int Y>
constexpr bool operator==(Elem<Y> const&) const
{ return X == Y; }
};
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();
// 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>{}));
}
}
// 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>{}));
}
}
// 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>{}));
}
}
// 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;
};
auto a = std::bind_front(add, m, n);
assert(a() == 3);
auto b = std::bind_front(addN, m, n, m, m, m, m);
assert(b() == 7);
auto c = std::bind_front(addN, n, m);
assert(c(1, 1, 1, 1) == 7);
auto f = std::bind_front(add, n);
assert(f(3) == 5);
auto g = std::bind_front(add, n, 1);
assert(g() == 3);
auto h = std::bind_front(addN, 1, 1, 1);
assert(h(2, 2, 2) == 9);
}
// Make sure we don't treat std::reference_wrapper specially.
{
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.
{
struct MemberFunction {
constexpr bool foo(int, int) { return true; }
};
MemberFunction value;
auto fn = std::bind_front(&MemberFunction::foo, value, 0);
assert(fn(0));
}
// Make sure that we copy the bound arguments into the unspecified-type.
{
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);
}
// 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)());
}
}
// 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);
}
// 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>);
}
}
// 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>);
}
}
// 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);
}
}
// 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;
}

View 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>