This patch reworks construction and assigment of function_ref from
other function_ref specialization:
* the constructor is implicit, and have rebinding semantic if
signatures are compatible (__is_funcref_convertible is true)
* the cosntructor is explicit and assigment is deleted, if
signatures are not compatible, and created function_ref refers
to function_ref.
The implementations moves the _M_ptrs members to newly defined base
class __polyfunc::_Ref_base. This allows us to reuse existing __base_of
and __invoker_of accessor in the implementation (after befriending them).
The accessors functions are also now marked as constexpr.
Furthermore we move _M_init function there, making it instantiations
independent from callback signature.
libstdc++-v3/ChangeLog:
* include/bits/cpyfunc_impl.h: (__polyfunc::__invoker_of)
(__polyfunc::_base_of): Mark as constexpr.
* include/bits/funcref_impl.h (__is_funcref_convertible): Define
partial specializations.
(std::function_ref): Add base class
of type__polyfunc::_Ref_base. Befriend __invoker_of, __base_of,
__is_invoker_convertible.
(function_ref::_Base): Define.
(function_ref::_M_init, function_ref::_M_ptrs): Move to base class.
(function_ref::function_ref(_Fn&&)): Handle specializations of
function_ref. Make conditionally explicit.
(function_ref::function_ref): Init base class before _M_invoke
consistently.
(function_ref::operator=): Excluded compatible function_ref.
* include/bits/funcwrap.h : (__polyfunc::__invoker_of)
(__polyfunc::_base_of): Mark as constexpr.
(__polyfunc::__is_function_ref_v, __is_funcref_convertible)
(__polyfunc::_Ref_base): Define.
* include/bits/mofunc_impl.h: (__polyfunc::__invoker_of)
(__polyfunc::_base_of): Mark as constexpr.
* testsuite/20_util/function_ref/conv.cc: Updated test to illustrate
that double indirection is avoided.
---
Not targeting trunk, posting as implementation exprience.
Tested on x86_64-linux locally. *function_ref* test passed with all
language modes.
libstdc++-v3/include/bits/cpyfunc_impl.h | 4 +-
libstdc++-v3/include/bits/funcref_impl.h | 52 +++++++++-----
libstdc++-v3/include/bits/funcwrap.h | 37 +++++++++-
libstdc++-v3/include/bits/mofunc_impl.h | 4 +-
.../testsuite/20_util/function_ref/conv.cc | 72 +++++++++++++++----
5 files changed, 132 insertions(+), 37 deletions(-)
diff --git a/libstdc++-v3/include/bits/cpyfunc_impl.h
b/libstdc++-v3/include/bits/cpyfunc_impl.h
index f1918ddf87a..4a89b66f1cb 100644
--- a/libstdc++-v3/include/bits/cpyfunc_impl.h
+++ b/libstdc++-v3/include/bits/cpyfunc_impl.h
@@ -252,11 +252,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
typename _Invoker::__storage_func_t _M_invoke = nullptr;
template<typename _Func>
- friend auto&
+ friend constexpr auto&
__polyfunc::__invoker_of(_Func&) noexcept;
template<typename _Func>
- friend auto&
+ friend constexpr auto&
__polyfunc::__base_of(_Func&) noexcept;
template<typename _Dst, typename _Src>
diff --git a/libstdc++-v3/include/bits/funcref_impl.h
b/libstdc++-v3/include/bits/funcref_impl.h
index 44c992281be..0525944b193 100644
--- a/libstdc++-v3/include/bits/funcref_impl.h
+++ b/libstdc++-v3/include/bits/funcref_impl.h
@@ -48,6 +48,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
noexcept(_Noex)>
{ using type = _Ret(_Args...) noexcept(_Noex); };
} // namespace __polyfunc
+
+ template<bool _Noex1, bool _Noex2, typename _Ret, typename... _Args>
+ constexpr bool __is_funcref_convertible<
+ function_ref<_Ret(_Args...) _GLIBCXX_MOF_CV
noexcept(_Noex1)>,
+ function_ref<_Ret(_Args...) _GLIBCXX_MOF_CV
noexcept(_Noex2)>>
+ = _Noex1 >= _Noex2;
/// @endcond
/**
@@ -67,11 +73,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
template<typename _Res, typename... _ArgTypes, bool _Noex>
class function_ref<_Res(_ArgTypes...) _GLIBCXX_MOF_CV
noexcept(_Noex)>
+ : __polyfunc::_Ref_base
{
static_assert(
(std::__is_complete_or_unbounded(__type_identity<_ArgTypes>()) && ...),
"each parameter type must be a complete class");
+ using _Base = __polyfunc::_Ref_base;
using _Invoker = __polyfunc::_Invoker<_Noex, _Res, _ArgTypes...>;
using _Signature = _Invoker::_Signature;
@@ -89,8 +97,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
function_ref(_Fn* __fn) noexcept
{
__glibcxx_assert(__fn != nullptr);
- _M_invoke = _Invoker::template _S_ptrs<_Fn*>();
_M_init(__fn);
+ _M_invoke = _Invoker::template _S_ptrs<_Fn*>();
}
/// Target and bound object is object referenced by parameter.
@@ -103,11 +111,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
// constant expression (above constructor is not constexpr).
&& (!is_function_v<_Vt>)
&& __is_invocable_using<_Vt _GLIBCXX_MOF_CV&>
- constexpr
+ constexpr explicit(!__is_funcref_convertible<remove_cv_t<_Vt>,
function_ref>)
function_ref(_Fn&& __f) noexcept
{
- _M_invoke = _Invoker::template _S_ptrs<_Vt _GLIBCXX_MOF_CV&>();
- _M_init(std::addressof(__f));
+ using _Vd = remove_cv_t<_Vt>;
+ if constexpr (__is_funcref_convertible<remove_cv_t<_Vt>,
function_ref>)
+ {
+ _Base::operator=(__polyfunc::__base_of(__f));
+ _M_invoke = __polyfunc::__invoker_of(__f);
+ }
+ else
+ {
+ using _Tr = _Vt _GLIBCXX_MOF_CV&;
+ _M_init(std::addressof(__f));
+ _M_invoke = _Invoker::template _S_ptrs<_Tr>();
+ }
}
// _GLIBCXX_RESOLVE_LIB_DEFECTS
@@ -122,8 +140,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
if constexpr (is_pointer_v<_Fn> || is_member_pointer_v<_Fn>)
static_assert(__fn != nullptr);
- _M_invoke = &_Invoker::template _S_nttp<__fn>;
_M_ptrs._M_obj = nullptr;
+ _M_invoke = &_Invoker::template _S_nttp<__fn>;
}
/// Target object is equivalent to std::bind_front<_fn>(std::ref(__ref)).
@@ -139,13 +157,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
static_assert(__fn != nullptr);
using _Tr = _Td _GLIBCXX_MOF_CV&;
+ _M_init(std::addressof(__ref));
if constexpr (is_member_pointer_v<_Fn> && is_lvalue_reference_v<_Tr>)
// N.B. invoking member pointer on lvalue produces the same effects,
// as invoking it on pointer to that lvalue.
_M_invoke = &_Invoker::template _S_bind_ptr<__fn, _Td
_GLIBCXX_MOF_CV>;
else
_M_invoke = &_Invoker::template _S_bind_ref<__fn, _Tr>;
- _M_init(std::addressof(__ref));
}
/// Target object is equivalent to std::bind_front<_fn>(__ptr).
@@ -161,12 +179,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
if constexpr (is_member_pointer_v<_Fn>)
__glibcxx_assert(__ptr != nullptr);
- _M_invoke = &_Invoker::template _S_bind_ptr<__fn, _Td
_GLIBCXX_MOF_CV>;
_M_init(__ptr);
+ _M_invoke = &_Invoker::template _S_bind_ptr<__fn, _Td
_GLIBCXX_MOF_CV>;
}
template<typename _Tp>
requires (!is_same_v<_Tp, function_ref>)
+ && (!__is_funcref_convertible<_Tp, function_ref>)
&& (!is_pointer_v<_Tp>) && (!__is_nontype_v<_Tp>)
function_ref&
operator=(_Tp) = delete;
@@ -182,18 +201,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
{ return _M_invoke(_M_ptrs, std::forward<_ArgTypes>(__args)...); }
private:
- template<typename _Tp>
- constexpr void
- _M_init(_Tp* __ptr) noexcept
- {
- if constexpr (is_function_v<_Tp>)
- _M_ptrs._M_func = reinterpret_cast<void(*)()>(__ptr);
- else
- _M_ptrs._M_obj = __ptr;
- }
-
typename _Invoker::__ptrs_func_t _M_invoke;
- __polyfunc::_Ptrs _M_ptrs;
+
+ template<typename _Func>
+ friend constexpr auto&
+ __polyfunc::__invoker_of(_Func&) noexcept;
+
+ template<typename _Func>
+ friend constexpr auto&
+ __polyfunc::__base_of(_Func&) noexcept;
};
#undef _GLIBCXX_MOF_CV
diff --git a/libstdc++-v3/include/bits/funcwrap.h
b/libstdc++-v3/include/bits/funcwrap.h
index 67fd591e963..8ba02078f15 100644
--- a/libstdc++-v3/include/bits/funcwrap.h
+++ b/libstdc++-v3/include/bits/funcwrap.h
@@ -223,12 +223,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
using _Invoker = _Base_invoker<_Noex, remove_cv_t<_Ret>,
__param_t<_Args>...>;
template<typename _Func>
- auto&
+ constexpr auto&
__invoker_of(_Func& __f) noexcept
{ return __f._M_invoke; }
template<typename _Func>
- auto&
+ constexpr auto&
__base_of(_Func& __f) noexcept
{ return static_cast<__like_t<_Func&, typename _Func::_Base>>(__f); }
@@ -530,6 +530,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
/// @cond undocumented
namespace __polyfunc
{
+ struct _Ref_base
+ {
+ template<typename _Tp>
+ constexpr void
+ _M_init(_Tp* __ptr) noexcept
+ {
+ if constexpr (is_function_v<_Tp>)
+ _M_ptrs._M_func = reinterpret_cast<void(*)()>(__ptr);
+ else
+ _M_ptrs._M_obj = __ptr;
+ }
+
+ _Ptrs _M_ptrs;
+ };
+
template<typename _Sig>
struct __skip_first_arg;
@@ -575,6 +590,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
function_ref(nontype_t<__f>, _Tp&&)
-> function_ref<remove_pointer_t<_SignaturePtr>>;
+ /// @cond undocumented
+ template<typename _Tp>
+ constexpr bool __is_function_ref_v = false;
+ template<typename _Tp>
+ constexpr bool __is_function_ref_v<function_ref<_Tp>> = true;
+
+ // Partial specializations are defined in bits/funcref_impl.h
+ template<typename _Func1, typename _Func2>
+ constexpr bool __is_funcref_convertible = false;
+
+ // Additional partial specializations are defined in bits/funcref_impl.h
+ template<bool _Noex1, bool _Noex2, typename _Ret, typename... _Args>
+ constexpr bool __is_funcref_convertible<
+ function_ref<_Ret(_Args...) const noexcept(_Noex1)>,
+ function_ref<_Ret(_Args...) noexcept(_Noex2)>>
+ = _Noex1 >= _Noex2;
+ /// @endcond
+
#endif // __glibcxx_function_ref
_GLIBCXX_END_NAMESPACE_VERSION
diff --git a/libstdc++-v3/include/bits/mofunc_impl.h
b/libstdc++-v3/include/bits/mofunc_impl.h
index 468e6855fc6..222e1782d14 100644
--- a/libstdc++-v3/include/bits/mofunc_impl.h
+++ b/libstdc++-v3/include/bits/mofunc_impl.h
@@ -236,11 +236,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
typename _Invoker::__storage_func_t _M_invoke = nullptr;
template<typename _Func>
- friend auto&
+ friend constexpr auto&
__polyfunc::__invoker_of(_Func&) noexcept;
template<typename _Func>
- friend auto&
+ friend constexpr auto&
__polyfunc::__base_of(_Func&) noexcept;
template<typename _Dst, typename _Src>
diff --git a/libstdc++-v3/testsuite/20_util/function_ref/conv.cc
b/libstdc++-v3/testsuite/20_util/function_ref/conv.cc
index 7606d265f98..78cade26285 100644
--- a/libstdc++-v3/testsuite/20_util/function_ref/conv.cc
+++ b/libstdc++-v3/testsuite/20_util/function_ref/conv.cc
@@ -36,6 +36,38 @@ static_assert( std::is_same_v<std::function_ref<void(int
const[5])>,
static_assert( std::is_same_v<std::function_ref<void(FuncType)>,
std::function_ref<void(FuncType*)>>);
+// Compatible signatures, conversion is implicit
+static_assert( std::is_convertible_v<std::function_ref<int(long) const
noexcept>,
+ std::function_ref<int(long) noexcept>> );
+static_assert( std::is_convertible_v<std::function_ref<int(long) const
noexcept>,
+ std::function_ref<int(long) const>> );
+static_assert( std::is_convertible_v<std::function_ref<int(long) const
noexcept>,
+ std::function_ref<int(long)>> );
+static_assert( std::is_assignable_v<std::function_ref<int(long) noexcept>&,
+ std::function_ref<int(long) const
noexcept>> );
+static_assert( std::is_assignable_v<std::function_ref<int(long) const>&,
+ std::function_ref<int(long) const
noexcept>> );
+static_assert( std::is_assignable_v<std::function_ref<int(long)>&,
+ std::function_ref<int(long) const
noexcept>> );
+
+// Incompatible signatures, conversion is explicit
+static_assert( !std::is_convertible_v<std::function_ref<int(long)>,
+ std::function_ref<int(long) const>> );
+static_assert( !std::is_convertible_v<std::function_ref<int(long)>,
+ std::function_ref<int(int)>> );
+static_assert( !std::is_convertible_v<std::function_ref<int(long)>,
+ std::function_ref<long(long)>> );
+static_assert( !std::is_convertible_v<std::function_ref<int(long)>,
+ std::function_ref<long(int)>> );
+static_assert( !std::is_assignable_v<std::function_ref<int(long) const>&,
+ std::function_ref<int(long)>> );
+static_assert( !std::is_assignable_v<std::function_ref<int(int)>&,
+ std::function_ref<int(long)>> );
+static_assert( !std::is_assignable_v<std::function_ref<long(long)>&,
+ std::function_ref<int(long)>> );
+static_assert( !std::is_assignable_v<std::function_ref<long(int)>&,
+ std::function_ref<int(long)>> );
+
// The C++26 [func.wrap.general] p2 does not currently cover funciton_ref,
// so we make extra copies of arguments.
@@ -54,26 +86,40 @@ test01()
VERIFY( r2c(c) == 2 );
std::function_ref<int(CountedArg) const> r3r(r1);
- VERIFY( r3r(c) == 2 );
+ VERIFY( r3r(c) == 1 );
std::function_ref<int(CountedArg) const> r3m(m1);
VERIFY( r3m(c) == 2 );
std::function_ref<int(CountedArg) const> r3c(c1);
VERIFY( r3c(c) == 2 );
- std::function_ref<int(CountedArg)> r4r(r1);
- VERIFY( r4r(c) == 2 );
- std::function_ref<int(CountedArg)> r4m(m1);
+ std::function_ref<int(CountedArg) noexcept> r4r(r1);
+ VERIFY( r4r(c) == 1 );
+ std::function_ref<int(CountedArg) noexcept> r4m(m1);
VERIFY( r4m(c) == 2 );
- std::function_ref<int(CountedArg)> r4c(c1);
+ std::function_ref<int(CountedArg) noexcept> r4c(c1);
VERIFY( r4c(c) == 2 );
+ std::function_ref<int(CountedArg)> r5r(r1);
+ VERIFY( r5r(c) == 1 );
+ std::function_ref<int(CountedArg)> r5m(m1);
+ VERIFY( r5m(c) == 2 );
+ std::function_ref<int(CountedArg)> r5c(c1);
+ VERIFY( r5c(c) == 2 );
+
+ r3r = r1;
+ VERIFY( r3r(c) == 1 );
+ r4r = r1;
+ VERIFY( r4r(c) == 1 );
+ r5r = r1;
+ VERIFY( r5r(c) == 1 );
+
// Incompatible signatures
- std::function_ref<long(CountedArg) const noexcept> r5r(r1);
- VERIFY( r5r(c) == 2 );
- std::function_ref<long(CountedArg) const noexcept> r5m(m1);
- VERIFY( r5r(c) == 2 );
- std::function_ref<long(CountedArg) const noexcept> r5c(c1);
- VERIFY( r5r(c) == 2 );
+ std::function_ref<long(CountedArg) const noexcept> r6r(r1);
+ VERIFY( r6r(c) == 2 );
+ std::function_ref<long(CountedArg) const noexcept> r6m(m1);
+ VERIFY( r6r(c) == 2 );
+ std::function_ref<long(CountedArg) const noexcept> r6c(c1);
+ VERIFY( r6r(c) == 2 );
}
void
@@ -110,7 +156,7 @@ test03()
// Call const overload as std::function_ref<int(CountedArg) const>
// inside std::function_ref<int(CountedArg)> would do.
std::function_ref<int(CountedArg)> r2(r1);
- VERIFY( r2(c) == 1002 );
+ VERIFY( r2(c) == 1001 );
std::move_only_function<int(CountedArg)> m2(r1);
VERIFY( m2(c) == 1002 );
@@ -234,7 +280,7 @@ test07()
std::function_ref<int()> r3r(r1);
VERIFY( r3r() == 2 );
r1 = f1;
- VERIFY( r3r() == 1 ); // converting-constructor
+ VERIFY( r3r() == 2 ); // rebinding constructor
std::function_ref<int()> r3m(m1);
VERIFY( r3m() == 2 );
--
2.51.0