[1/3] libstdc++: Separate construct/convertibility tests for std::tuple

Message ID 20220823013500.1756466-1-ppalka@redhat.com
State New, archived
Headers
Series [1/3] libstdc++: Separate construct/convertibility tests for std::tuple |

Commit Message

Patrick Palka Aug. 23, 2022, 1:34 a.m. UTC
  P2321R2 adds new conditionally explicit constructors to std::tuple which
we'll concisely implement in a subsequent patch using explicit(bool), like
in our C++20 std::pair implementation.  But before we can do that, this
patch first adds members to _TupleConstraints that test for constructibility
and convertibility separately; we'll use the first in the new constructors'
constraints, and the second in their explicit specifier.

In passing, this patch also redefines the existing predicates
__is_ex/implicitly_constructible in terms of these new members.  This
seems to reduce compile time and memory usage by about 10% for large
tuples when using the relevant constructors constrained by
_Explicit/_ImplicitCtor (since we no longer have to redundantly expand
and process is_constructible<_Types, _UTypes>... twice for each pair of
such constructors).  In order to retain maximal short circuiting, do
this only when constexpr if is available.

Tested on x86_64-pc-linux-gnu, does this look OK for trunk?

libstdc++-v3/ChangeLog:

	* include/std/tuple (_TupleConstraints::__convertible): Define
	for C++17.
	(_TupleConstraints::__constructible): Likewise.
	(_TupleConstraints::__is_explicitly_constructible): For C++17
	define this in terms of __convertible and __constructible
	using constexpr if.
	(_TupleConstraints::__is_implicitly_constructible): Likewise.
---
 libstdc++-v3/include/std/tuple | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)
  

Comments

Jonathan Wakely Aug. 23, 2022, 9:15 a.m. UTC | #1
On Tue, 23 Aug 2022 at 02:35, Patrick Palka via Libstdc++
<libstdc++@gcc.gnu.org> wrote:
>
> P2321R2 adds new conditionally explicit constructors to std::tuple which
> we'll concisely implement in a subsequent patch using explicit(bool), like
> in our C++20 std::pair implementation.  But before we can do that, this
> patch first adds members to _TupleConstraints that test for constructibility
> and convertibility separately; we'll use the first in the new constructors'
> constraints, and the second in their explicit specifier.
>
> In passing, this patch also redefines the existing predicates
> __is_ex/implicitly_constructible in terms of these new members.  This
> seems to reduce compile time and memory usage by about 10% for large

Nice.

> tuples when using the relevant constructors constrained by
> _Explicit/_ImplicitCtor (since we no longer have to redundantly expand
> and process is_constructible<_Types, _UTypes>... twice for each pair of
> such constructors).  In order to retain maximal short circuiting, do
> this only when constexpr if is available.

Would we get similar benefits for C++11 and C++14 by doing:

       return __and_<__and_<is_constructible<_Types, _UTypes>...>,
                     __and_<is_convertible<_UTypes, _Types>...>
                     >::value;

This is slightly more work in total, but if we have __and_<A,B> and
__and_<A,__not_<B>> then the A and B instantiations will be cached and
can be reused.


> Tested on x86_64-pc-linux-gnu, does this look OK for trunk?

Yes, thanks.
  
Patrick Palka Aug. 23, 2022, 1:44 p.m. UTC | #2
On Tue, 23 Aug 2022, Jonathan Wakely wrote:

> On Tue, 23 Aug 2022 at 02:35, Patrick Palka via Libstdc++
> <libstdc++@gcc.gnu.org> wrote:
> >
> > P2321R2 adds new conditionally explicit constructors to std::tuple which
> > we'll concisely implement in a subsequent patch using explicit(bool), like
> > in our C++20 std::pair implementation.  But before we can do that, this
> > patch first adds members to _TupleConstraints that test for constructibility
> > and convertibility separately; we'll use the first in the new constructors'
> > constraints, and the second in their explicit specifier.
> >
> > In passing, this patch also redefines the existing predicates
> > __is_ex/implicitly_constructible in terms of these new members.  This
> > seems to reduce compile time and memory usage by about 10% for large
> 
> Nice.
> 
> > tuples when using the relevant constructors constrained by
> > _Explicit/_ImplicitCtor (since we no longer have to redundantly expand
> > and process is_constructible<_Types, _UTypes>... twice for each pair of
> > such constructors).  In order to retain maximal short circuiting, do
> > this only when constexpr if is available.
> 
> Would we get similar benefits for C++11 and C++14 by doing:
> 
>        return __and_<__and_<is_constructible<_Types, _UTypes>...>,
>                      __and_<is_convertible<_UTypes, _Types>...>
>                      >::value;
> 
> This is slightly more work in total, but if we have __and_<A,B> and
> __and_<A,__not_<B>> then the A and B instantiations will be cached and
> can be reused.

Good idea, it seems we get pretty much the same 10% improvement for
C++11/14 with this approach.  I reckon we might as well then define
__convertible and __constructible as alias templates instead of as
variable templates and use them unconditionally in
__is_im/explicitly_constructible for benefit of all language versions.
Using constexpr if instead of the outer __and_ seems to yield a marginal
improvement at best and __and_v is currently just __and_::value, so it
doesn't seem worth it to have different definitions for C++17 at least
for now.

What do you think about the following?

-- >8 --

libstdc++-v3/ChangeLog:

	* include/std/tuple (_TupleConstraints::__convertible): Define.
	(_TupleConstraints::__constructible): Likewise.
	(_TupleConstraints::__is_explicitly_constructible): Redefine this
	in terms of __convertible and __constructible.
	(_TupleConstraints::__is_implicitly_constructible): Likewise.
---
 libstdc++-v3/include/std/tuple | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
index 6d0060a191c..f8f48ccc370 100644
--- a/libstdc++-v3/include/std/tuple
+++ b/libstdc++-v3/include/std/tuple
@@ -553,15 +553,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   template<bool, typename... _Types>
     struct _TupleConstraints
     {
+      template<typename... _UTypes>
+	using __constructible = __and_<is_constructible<_Types, _UTypes>...>;
+
+      template<typename... _UTypes>
+	using __convertible = __and_<is_convertible<_UTypes, _Types>...>;
+
       // Constraint for a non-explicit constructor.
       // True iff each Ti in _Types... can be constructed from Ui in _UTypes...
       // and every Ui is implicitly convertible to Ti.
       template<typename... _UTypes>
 	static constexpr bool __is_implicitly_constructible()
 	{
-	  return __and_<is_constructible<_Types, _UTypes>...,
-			is_convertible<_UTypes, _Types>...
-			>::value;
+	  return __and_<__constructible<_UTypes...>,
+			__convertible<_UTypes...>>::value;
 	}
 
       // Constraint for a non-explicit constructor.
@@ -570,9 +575,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename... _UTypes>
 	static constexpr bool __is_explicitly_constructible()
 	{
-	  return __and_<is_constructible<_Types, _UTypes>...,
-			__not_<__and_<is_convertible<_UTypes, _Types>...>>
-			>::value;
+	  return __and_<__constructible<_UTypes...>,
+			__not_<__convertible<_UTypes...>>>::value;
 	}
 
       static constexpr bool __is_implicitly_default_constructible()
  
Jonathan Wakely Aug. 23, 2022, 2:53 p.m. UTC | #3
On Tue, 23 Aug 2022 at 14:44, Patrick Palka <ppalka@redhat.com> wrote:
>
> On Tue, 23 Aug 2022, Jonathan Wakely wrote:
>
> > On Tue, 23 Aug 2022 at 02:35, Patrick Palka via Libstdc++
> > <libstdc++@gcc.gnu.org> wrote:
> > >
> > > P2321R2 adds new conditionally explicit constructors to std::tuple which
> > > we'll concisely implement in a subsequent patch using explicit(bool), like
> > > in our C++20 std::pair implementation.  But before we can do that, this
> > > patch first adds members to _TupleConstraints that test for constructibility
> > > and convertibility separately; we'll use the first in the new constructors'
> > > constraints, and the second in their explicit specifier.
> > >
> > > In passing, this patch also redefines the existing predicates
> > > __is_ex/implicitly_constructible in terms of these new members.  This
> > > seems to reduce compile time and memory usage by about 10% for large
> >
> > Nice.
> >
> > > tuples when using the relevant constructors constrained by
> > > _Explicit/_ImplicitCtor (since we no longer have to redundantly expand
> > > and process is_constructible<_Types, _UTypes>... twice for each pair of
> > > such constructors).  In order to retain maximal short circuiting, do
> > > this only when constexpr if is available.
> >
> > Would we get similar benefits for C++11 and C++14 by doing:
> >
> >        return __and_<__and_<is_constructible<_Types, _UTypes>...>,
> >                      __and_<is_convertible<_UTypes, _Types>...>
> >                      >::value;
> >
> > This is slightly more work in total, but if we have __and_<A,B> and
> > __and_<A,__not_<B>> then the A and B instantiations will be cached and
> > can be reused.
>
> Good idea, it seems we get pretty much the same 10% improvement for
> C++11/14 with this approach.  I reckon we might as well then define
> __convertible and __constructible as alias templates instead of as
> variable templates and use them unconditionally in
> __is_im/explicitly_constructible for benefit of all language versions.

I had a similar thought after hitting send.


> Using constexpr if instead of the outer __and_ seems to yield a marginal
> improvement at best and __and_v is currently just __and_::value, so it
> doesn't seem worth it to have different definitions for C++17 at least
> for now.
>
> What do you think about the following?

OK for trunk - thanks.
  

Patch

diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
index 6d0060a191c..d0c168fd7e2 100644
--- a/libstdc++-v3/include/std/tuple
+++ b/libstdc++-v3/include/std/tuple
@@ -553,15 +553,31 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
   template<bool, typename... _Types>
     struct _TupleConstraints
     {
+#if __cplusplus >= 201703L
+      template<typename... _UTypes>
+	static constexpr bool __constructible
+	  = __and_v<is_constructible<_Types, _UTypes>...>;
+
+      template<typename... _UTypes>
+	static constexpr bool __convertible
+	  = __and_v<is_convertible<_UTypes, _Types>...>;
+#endif
+
       // Constraint for a non-explicit constructor.
       // True iff each Ti in _Types... can be constructed from Ui in _UTypes...
       // and every Ui is implicitly convertible to Ti.
       template<typename... _UTypes>
 	static constexpr bool __is_implicitly_constructible()
 	{
+#if __cplusplus >= 201703L
+	  if constexpr (__constructible<_UTypes...>)
+	    return __convertible<_UTypes...>;
+	  return false;
+#else
 	  return __and_<is_constructible<_Types, _UTypes>...,
 			is_convertible<_UTypes, _Types>...
 			>::value;
+#endif
 	}
 
       // Constraint for a non-explicit constructor.
@@ -570,9 +586,15 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename... _UTypes>
 	static constexpr bool __is_explicitly_constructible()
 	{
+#if __cplusplus >= 201703L
+	  if constexpr (__constructible<_UTypes...>)
+	    return !__convertible<_UTypes...>;
+	  return false;
+#else
 	  return __and_<is_constructible<_Types, _UTypes>...,
 			__not_<__and_<is_convertible<_UTypes, _Types>...>>
 			>::value;
+#endif
 	}
 
       static constexpr bool __is_implicitly_default_constructible()