forked from OSchip/llvm-project
[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:
parent
d232ec3c2a
commit
f599e7a789
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
Loading…
Reference in New Issue