[PATCH] Constrain std::shared_ptr assignment and resetting

Jonathan Wakely jwakely@redhat.com
Wed Aug 31 17:04:00 GMT 2016


The standard subtly requires us to constrain the assignment operators,
due to "Effects: Equivalent to [a constrained expression]".

Constraining reset() is not required, but a private discussion started
by Alisdair Meredith has convinced me that it's useful (and apparently
libc++ already does this).

	* include/bits/shared_ptr.h (_Assignable): New alias template.
	(shared_ptr::operator=(const shared_ptr<_Tp1>&))
	(shared_ptr::operator=(shared_ptr<_Tp1>&&))
	(shared_ptr::operator=(unique_ptr<_Tp1>&&)): Constrain with
	_Assignable.
	* include/bits/shared_ptr_base.h (_Assignable): New alias template.
	(__shared_ptr::operator=(const __shared_ptr<_Tp1>&))
	(__shared_ptr::operator=(__shared_ptr<_Tp1>&&))
	(__shared_ptr::operator=(unique_ptr<_Tp1>&&)): Constrain with
	_Assignable.
	(__shared_ptr::reset(_Tp1*), __shared_ptr::reset(_Tp1*, _Deleter))
	(__shared_ptr::reset(_Tp1*, _Deleter, _Alloc)): Constrain with
	_Convertible.
	* testsuite/20_util/shared_ptr/cons/43820_neg.cc: Change dg-error to
	match on any line.
	* testsuite/20_util/shared_ptr/cons/void_neg.cc: Likewise.
	* testsuite/20_util/shared_ptr/assign/sfinae.cc: New test.
	* testsuite/20_util/shared_ptr/assign/shared_ptr_neg.cc: Update
	expected errors. Remove unnecessary code.
	* testsuite/20_util/shared_ptr/modifiers/reset_sfinae.cc: New test.

Tested powerpc64le-linux, committed to trunk.

-------------- next part --------------
commit 1c8d8d7ddb83f6205137e4e62266f7c361158ef3
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Wed Aug 31 14:15:38 2016 +0100

    Constrain std::shared_ptr assignment and resetting
    
    	* include/bits/shared_ptr.h (_Assignable): New alias template.
    	(shared_ptr::operator=(const shared_ptr<_Tp1>&))
    	(shared_ptr::operator=(shared_ptr<_Tp1>&&))
    	(shared_ptr::operator=(unique_ptr<_Tp1>&&)): Constrain with
    	_Assignable.
    	* include/bits/shared_ptr_base.h (_Assignable): New alias template.
    	(__shared_ptr::operator=(const __shared_ptr<_Tp1>&))
    	(__shared_ptr::operator=(__shared_ptr<_Tp1>&&))
    	(__shared_ptr::operator=(unique_ptr<_Tp1>&&)): Constrain with
    	_Assignable.
    	(__shared_ptr::reset(_Tp1*), __shared_ptr::reset(_Tp1*, _Deleter))
    	(__shared_ptr::reset(_Tp1*, _Deleter, _Alloc)): Constrain with
    	_Convertible.
    	* testsuite/20_util/shared_ptr/cons/43820_neg.cc: Change dg-error to
    	match on any line.
    	* testsuite/20_util/shared_ptr/cons/void_neg.cc: Likewise.
    	* testsuite/20_util/shared_ptr/assign/sfinae.cc: New test.
    	* testsuite/20_util/shared_ptr/assign/shared_ptr_neg.cc: Update
    	expected errors. Remove unnecessary code.
    	* testsuite/20_util/shared_ptr/modifiers/reset_sfinae.cc: New test.

diff --git a/libstdc++-v3/include/bits/shared_ptr.h b/libstdc++-v3/include/bits/shared_ptr.h
index 747b09a..b2523b8 100644
--- a/libstdc++-v3/include/bits/shared_ptr.h
+++ b/libstdc++-v3/include/bits/shared_ptr.h
@@ -93,8 +93,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
     class shared_ptr : public __shared_ptr<_Tp>
     {
       template<typename _Ptr>
-	using _Convertible
-	  = typename enable_if<is_convertible<_Ptr, _Tp*>::value>::type;
+	using _Convertible = typename
+	  enable_if<is_convertible<_Ptr, _Tp*>::value>::type;
+
+      template<typename _Ptr>
+	using _Assignable = typename
+	  enable_if<is_convertible<_Ptr, _Tp*>::value, shared_ptr&>::type;
 
     public:
 
@@ -276,7 +280,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       shared_ptr& operator=(const shared_ptr&) noexcept = default;
 
       template<typename _Tp1>
-	shared_ptr&
+	_Assignable<_Tp1*>
 	operator=(const shared_ptr<_Tp1>& __r) noexcept
 	{
 	  this->__shared_ptr<_Tp>::operator=(__r);
@@ -301,7 +305,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       }
 
       template<class _Tp1>
-	shared_ptr&
+	_Assignable<_Tp1*>
 	operator=(shared_ptr<_Tp1>&& __r) noexcept
 	{
 	  this->__shared_ptr<_Tp>::operator=(std::move(__r));
@@ -309,7 +313,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 
       template<typename _Tp1, typename _Del>
-	shared_ptr&
+	_Assignable<typename unique_ptr<_Tp1, _Del>::pointer>
 	operator=(std::unique_ptr<_Tp1, _Del>&& __r)
 	{
 	  this->__shared_ptr<_Tp>::operator=(std::move(__r));
diff --git a/libstdc++-v3/include/bits/shared_ptr_base.h b/libstdc++-v3/include/bits/shared_ptr_base.h
index 60b825c..4ae2668 100644
--- a/libstdc++-v3/include/bits/shared_ptr_base.h
+++ b/libstdc++-v3/include/bits/shared_ptr_base.h
@@ -873,6 +873,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	using _Convertible
 	  = typename enable_if<is_convertible<_Ptr, _Tp*>::value>::type;
 
+      template<typename _Ptr>
+	using _Assignable = typename
+	  enable_if<is_convertible<_Ptr, _Tp*>::value, __shared_ptr&>::type;
+
     public:
       typedef _Tp   element_type;
 
@@ -983,7 +987,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       constexpr __shared_ptr(nullptr_t) noexcept : __shared_ptr() { }
 
       template<typename _Tp1>
-	__shared_ptr&
+	_Assignable<_Tp1*>
 	operator=(const __shared_ptr<_Tp1, _Lp>& __r) noexcept
 	{
 	  _M_ptr = __r._M_ptr;
@@ -1009,7 +1013,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       }
 
       template<class _Tp1>
-	__shared_ptr&
+	_Assignable<_Tp1*>
 	operator=(__shared_ptr<_Tp1, _Lp>&& __r) noexcept
 	{
 	  __shared_ptr(std::move(__r)).swap(*this);
@@ -1017,7 +1021,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 
       template<typename _Tp1, typename _Del>
-	__shared_ptr&
+	_Assignable<typename unique_ptr<_Tp1, _Del>::pointer>
 	operator=(std::unique_ptr<_Tp1, _Del>&& __r)
 	{
 	  __shared_ptr(std::move(__r)).swap(*this);
@@ -1029,7 +1033,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       { __shared_ptr().swap(*this); }
 
       template<typename _Tp1>
-	void
+	_Convertible<_Tp1*>
 	reset(_Tp1* __p) // _Tp1 must be complete.
 	{
 	  // Catch self-reset errors.
@@ -1038,12 +1042,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	}
 
       template<typename _Tp1, typename _Deleter>
-	void
+	_Convertible<_Tp1*>
 	reset(_Tp1* __p, _Deleter __d)
 	{ __shared_ptr(__p, __d).swap(*this); }
 
       template<typename _Tp1, typename _Deleter, typename _Alloc>
-	void
+	_Convertible<_Tp1*>
         reset(_Tp1* __p, _Deleter __d, _Alloc __a)
         { __shared_ptr(__p, __d, std::move(__a)).swap(*this); }
 
diff --git a/libstdc++-v3/testsuite/20_util/shared_ptr/assign/sfinae.cc b/libstdc++-v3/testsuite/20_util/shared_ptr/assign/sfinae.cc
new file mode 100644
index 0000000..d79af04
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/shared_ptr/assign/sfinae.cc
@@ -0,0 +1,75 @@
+// Copyright (C) 2016 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-do compile { target c++11 } }
+
+#include <memory>
+
+template<typename T, typename From>
+constexpr bool can_assign()
+{ return std::is_assignable<std::shared_ptr<T>, From>::value; }
+
+struct Base { };
+struct Derived : Base { };
+
+// Positive cases:
+
+static_assert( can_assign<const void, const std::shared_ptr<void>&>(),
+    "void* convertible to const void*");
+static_assert( can_assign<const void, std::shared_ptr<void>&&>(),
+    "void* convertible to const void*");
+static_assert( can_assign<const int, std::shared_ptr<int>>(),
+    "int* convertible to const int*");
+static_assert( can_assign<Base, std::shared_ptr<Derived>>(),
+    "Derived* convertible to Base*");
+static_assert( can_assign<const Base, std::shared_ptr<Derived>>(),
+    "Derived* convertible to const Base*");
+
+// Negative cases:
+
+static_assert( !can_assign<int, const std::shared_ptr<void>&>(),
+    "void* not convertible to int*");
+static_assert( !can_assign<int, std::shared_ptr<void>&&>(),
+    "void* not convertible to int*");
+
+static_assert( !can_assign<int, const std::shared_ptr<const int>&>(),
+    "const int* not convertible to int*");
+static_assert( !can_assign<int, std::shared_ptr<const int>&&>(),
+    "const int* not convertible to int*");
+
+static_assert( !can_assign<int, const std::shared_ptr<long>&>(),
+    "long* not convertible to int*");
+static_assert( !can_assign<int, std::shared_ptr<long>&&>(),
+    "long* not convertible to int*");
+
+static_assert( !can_assign<int, std::unique_ptr<long>&&>(),
+    "unique_ptr<long>::pointer not convertible to int*");
+
+static_assert( !can_assign<Derived, const std::shared_ptr<Base>&>(),
+    "Base* not convertible to Derived*");
+static_assert( !can_assign<int, std::shared_ptr<long>&&>(),
+    "Base* not convertible to Derived*");
+static_assert( !can_assign<Derived, std::unique_ptr<Base>&&>(),
+    "unique_ptr<Base>::pointer not convertible to Derived*");
+
+struct Deleter {
+  using pointer = void*;
+  void operator()(pointer) const { }
+};
+
+static_assert( !can_assign<Derived, std::unique_ptr<Derived, Deleter>&&>(),
+    "unique_ptr<Derived, Deleter>::pointer not convertible to Derived*");
diff --git a/libstdc++-v3/testsuite/20_util/shared_ptr/assign/shared_ptr_neg.cc b/libstdc++-v3/testsuite/20_util/shared_ptr/assign/shared_ptr_neg.cc
index 751495a..96f07b5 100644
--- a/libstdc++-v3/testsuite/20_util/shared_ptr/assign/shared_ptr_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/shared_ptr/assign/shared_ptr_neg.cc
@@ -28,24 +28,10 @@ struct B { };
 // 20.6.6.2.3 shared_ptr assignment [util.smartptr.shared.assign]
 
 // Assignment from incompatible shared_ptr<Y>
-int
+void
 test01()
 {
-  bool test __attribute__((unused)) = true;
-
   std::shared_ptr<A> a;
   std::shared_ptr<B> b;
-  a = b;                      // { dg-error "here" }
-
-  return 0;
+  a = b;                      // { dg-error "no match" }
 }
-
-int 
-main()
-{
-  test01();
-  return 0;
-}
-// { dg-error "In instantiation" "" { target *-*-* } 0 }
-// { dg-error "cannot convert" "" { target *-*-* } 0 }
-// { dg-error "required from" "" { target *-*-* } 0 }
diff --git a/libstdc++-v3/testsuite/20_util/shared_ptr/cons/43820_neg.cc b/libstdc++-v3/testsuite/20_util/shared_ptr/cons/43820_neg.cc
index 530256a..c58c842 100644
--- a/libstdc++-v3/testsuite/20_util/shared_ptr/cons/43820_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/shared_ptr/cons/43820_neg.cc
@@ -32,8 +32,6 @@ void test01()
 {
   X* px = 0;
   std::shared_ptr<X> p1(px);   // { dg-error "here" }
-  // { dg-error "incomplete" "" { target *-*-* } 893 }
-
   std::shared_ptr<X> p9(ap());  // { dg-error "here" }
-  // { dg-error "incomplete" "" { target *-*-* } 307 }
+  // { dg-error "incomplete" "" { target *-*-* } 0 }
 }
diff --git a/libstdc++-v3/testsuite/20_util/shared_ptr/cons/void_neg.cc b/libstdc++-v3/testsuite/20_util/shared_ptr/cons/void_neg.cc
index f77ddec..0cadf25 100644
--- a/libstdc++-v3/testsuite/20_util/shared_ptr/cons/void_neg.cc
+++ b/libstdc++-v3/testsuite/20_util/shared_ptr/cons/void_neg.cc
@@ -24,5 +24,5 @@
 void test01()
 {
   std::shared_ptr<void> p((void*)nullptr);   // { dg-error "here" }
-  // { dg-error "incomplete" "" { target *-*-* } 892 }
+  // { dg-error "incomplete" "" { target *-*-* } 0 }
 }
diff --git a/libstdc++-v3/testsuite/20_util/shared_ptr/modifiers/reset_sfinae.cc b/libstdc++-v3/testsuite/20_util/shared_ptr/modifiers/reset_sfinae.cc
new file mode 100644
index 0000000..f75530f
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/shared_ptr/modifiers/reset_sfinae.cc
@@ -0,0 +1,92 @@
+// Copyright (C) 2016 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-do compile { target c++11 } }
+
+#include <memory>
+
+template<typename T, typename Args, typename = void>
+  struct resettable
+  : std::false_type
+  { };
+
+template<typename... T> struct type_list { };
+
+template<typename T, typename... Args>
+  using reset_result
+    = decltype(std::shared_ptr<T>{}.reset(std::declval<Args>()...));
+
+template<typename T, typename... Args>
+  struct resettable<T, type_list<Args...>, reset_result<T, Args...>>
+  : std::true_type
+  { };
+
+template<typename T, typename... Args>
+constexpr bool can_reset()
+{ return resettable<T, type_list<Args...>>::value; }
+
+template<typename T>
+struct Deleter {
+  void operator()(T*) const;
+};
+
+template<typename T>
+using Alloc = std::allocator<T>;
+
+struct Base { };
+struct Derived : Base { };
+
+// Positive cases:
+
+static_assert( can_reset<const void, void*>(),
+    "void* convertible to const void*");
+static_assert( can_reset<const int, int*>(),
+    "int* convertible to const int*");
+static_assert( can_reset<Base, Derived*>(),
+    "Derived* convertible to Base*");
+static_assert( can_reset<const Base, Derived*>(),
+    "Derived* convertible to const Base*");
+
+// Negative cases:
+
+static_assert( !can_reset<int, void*>(),
+    "void* not convertible to int*");
+static_assert( !can_reset<int, void*, Deleter<int>>(),
+    "void* not convertible to int*");
+static_assert( !can_reset<int, void*, Deleter<int>, Alloc<int>>(),
+    "void* not convertible to int*");
+
+static_assert( !can_reset<int, const int*>(),
+    "const int* not convertible to int*");
+static_assert( !can_reset<int, const int*, Deleter<int>>(),
+    "const int* not convertible to int*");
+static_assert( !can_reset<int, const int*, Deleter<int>, Alloc<int>>(),
+    "const int* not convertible to int*");
+
+static_assert( !can_reset<int, long*>(),
+    "long* not convertible to int*");
+static_assert( !can_reset<int, long*, Deleter<int>>(),
+    "long* not convertible to int*");
+static_assert( !can_reset<int, long*, Deleter<int>, Alloc<int>>(),
+    "long* not convertible to int*");
+
+static_assert( !can_reset<Derived, Base*>(),
+    "Base* not convertible to Derived*");
+static_assert( !can_reset<Derived, Base*, Deleter<int>>(),
+    "Base* not convertible to Derived*");
+static_assert( !can_reset<Derived, Base*, Deleter<int>, Alloc<int>>(),
+    "Base* not convertible to Derived*");


More information about the Gcc-patches mailing list