libstdc++: Use conditional noexcept in std::reverse_iterator [PR 94418]
authorJonathan Wakely <jwakely@redhat.com>
Wed, 28 Apr 2021 10:40:47 +0000 (11:40 +0100)
committerJonathan Wakely <jwakely@redhat.com>
Fri, 1 Oct 2021 19:34:46 +0000 (20:34 +0100)
This adds a noexcept-specifier to each constructor and assignment
operator of std::reverse_iterator so that they are noexcept when the
corresponding operation on the underlying iterator is noexcept.

The std::reverse_iterator class template already requires that the
operations on the underlying type are valid, so we don't need to use the
std::is_nothrow_xxx traits to protect against errors when the expression
isn't even valid. We can just use a noexcept operator to test if the
expression can throw, without the overhead of redundantly checking if
the initialization/assignment would be valid.

Signed-off-by: Jonathan Wakely <jwakely@redhat.com>
libstdc++-v3/ChangeLog:

PR libstdc++/94418
* include/bits/stl_iterator.h (reverse_iterator): Use
conditional noexcept on constructors and assignment operators.
* testsuite/24_iterators/reverse_iterator/noexcept.cc: New test.

libstdc++-v3/include/bits/stl_iterator.h
libstdc++-v3/testsuite/24_iterators/reverse_iterator/noexcept.cc [new file with mode: 0644]

index 004d767..4973f79 100644 (file)
@@ -174,20 +174,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       // 235 No specification of default ctor for reverse_iterator
       // 1012. reverse_iterator default ctor should value initialize
       _GLIBCXX17_CONSTEXPR
-      reverse_iterator() : current() { }
+      reverse_iterator()
+      _GLIBCXX_NOEXCEPT_IF(noexcept(_Iterator()))
+      : current()
+      { }
 
       /**
        *  This %iterator will move in the opposite direction that @p x does.
       */
       explicit _GLIBCXX17_CONSTEXPR
-      reverse_iterator(iterator_type __x) : current(__x) { }
+      reverse_iterator(iterator_type __x)
+      _GLIBCXX_NOEXCEPT_IF(noexcept(_Iterator(__x)))
+      : current(__x)
+      { }
 
       /**
        *  The copy constructor is normal.
       */
       _GLIBCXX17_CONSTEXPR
       reverse_iterator(const reverse_iterator& __x)
-      : current(__x.current) { }
+      _GLIBCXX_NOEXCEPT_IF(noexcept(_Iterator(__x.current)))
+      : current(__x.current)
+      { }
 
 #if __cplusplus >= 201103L
       reverse_iterator& operator=(const reverse_iterator&) = default;
@@ -203,7 +211,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 #endif
        _GLIBCXX17_CONSTEXPR
         reverse_iterator(const reverse_iterator<_Iter>& __x)
-       : current(__x.current) { }
+       _GLIBCXX_NOEXCEPT_IF(noexcept(_Iterator(__x.current)))
+       : current(__x.current)
+       { }
 
 #if __cplusplus >= 201103L
       template<typename _Iter>
@@ -214,6 +224,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        _GLIBCXX17_CONSTEXPR
        reverse_iterator&
        operator=(const reverse_iterator<_Iter>& __x)
+       _GLIBCXX_NOEXCEPT_IF(noexcept(current = __x.current))
        {
          current = __x.current;
          return *this;
@@ -226,6 +237,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       _GLIBCXX_NODISCARD
       _GLIBCXX17_CONSTEXPR iterator_type
       base() const
+      _GLIBCXX_NOEXCEPT_IF(noexcept(_Iterator(current)))
       { return current; }
 
       /**
diff --git a/libstdc++-v3/testsuite/24_iterators/reverse_iterator/noexcept.cc b/libstdc++-v3/testsuite/24_iterators/reverse_iterator/noexcept.cc
new file mode 100644 (file)
index 0000000..df4b1b0
--- /dev/null
@@ -0,0 +1,92 @@
+// { dg-do compile { target c++11 } }
+
+#include <iterator>
+
+template<typename T, bool Nothrow>
+struct bidi
+{
+  using value_type = T;
+  using pointer = T*;
+  using reference = T&;
+  using difference_type = std::ptrdiff_t;
+  using iterator_category = std::bidirectional_iterator_tag;
+
+  T* ptr;
+
+  bidi(T* ptr = nullptr) noexcept(Nothrow) : ptr(ptr) { }
+
+  bidi(const bidi& iter) noexcept(Nothrow) : ptr(iter.ptr) { }
+
+  template<typename U>
+    bidi(const bidi<U, Nothrow>& iter) noexcept(Nothrow) : ptr(iter.ptr) { }
+
+  bidi& operator=(const bidi& iter) noexcept(Nothrow)
+  {
+    ptr = iter.ptr;
+    return *this;
+  }
+
+  template<typename U>
+  bidi& operator=(const bidi<U, Nothrow>& iter) noexcept(Nothrow)
+  {
+    ptr = iter.ptr;
+    return *this;
+  }
+
+  bidi& operator++() { ++ptr; return *this; }
+  bidi& operator--() { --ptr; return *this; }
+  bidi operator++(int) { bidi tmp = *this; ++ptr; return tmp; }
+  bidi operator--(int) { bidi tmp = *this; --ptr; return tmp; }
+
+  reference operator*() const { return *ptr; }
+  pointer operator->() const { return ptr; }
+};
+
+void
+test01()
+{
+  using B1 = bidi<int, true>;
+  using R1 = std::reverse_iterator<B1>;
+  static_assert( std::is_nothrow_default_constructible<R1>(), "" );
+  static_assert( std::is_nothrow_copy_constructible<R1>(), "" );
+  static_assert( std::is_nothrow_move_constructible<R1>(), "" );
+  static_assert( std::is_nothrow_copy_assignable<R1>(), "" );
+  static_assert( std::is_nothrow_move_assignable<R1>(), "" );
+  static_assert( std::is_nothrow_constructible<R1, const B1&>(), "" );
+  static_assert( std::is_nothrow_constructible<R1, B1>(), "" );
+
+  using B2 = bidi<const int, true>;
+  using R2 = std::reverse_iterator<B2>;
+  // Test conversions from reverse_iterator<B1> to reverse_iterator<B2>.
+  static_assert( std::is_nothrow_constructible<R2, const R1&>(), "" );
+  static_assert( std::is_nothrow_assignable<R2&, const R1&>(), "" );
+  // And from B1 to reverse_iterator<B2>.
+  static_assert( std::is_nothrow_constructible<R2, const B2&>(), "" );
+  static_assert( std::is_nothrow_constructible<R2, B2>(), "" );
+  static_assert( std::is_nothrow_constructible<R2, const B1&>(), "" );
+  static_assert( std::is_nothrow_constructible<R2, B1>(), "" );
+
+  using B3 = bidi<int, false>;
+  using R3 = std::reverse_iterator<B3>;
+  static_assert( ! std::is_nothrow_default_constructible<R3>(), "" );
+  static_assert( ! std::is_nothrow_copy_constructible<R3>(), "" );
+  static_assert( ! std::is_nothrow_move_constructible<R3>(), "" );
+  static_assert( ! std::is_nothrow_copy_assignable<R3>(), "" );
+  static_assert( ! std::is_nothrow_move_assignable<R3>(), "" );
+  static_assert( ! std::is_nothrow_constructible<R3, const B3&>(), "" );
+  static_assert( ! std::is_nothrow_constructible<R3, B3>(), "" );
+
+  using B4 = bidi<const int, false>;
+  using R4 = std::reverse_iterator<B4>;
+  // Test conversions from reverse_iterator<B3> to reverse_iterator<B4>.
+  static_assert( ! std::is_nothrow_constructible<R4, const R3&>(), "" );
+  static_assert( ! std::is_nothrow_assignable<R4&, const R3&>(), "" );
+  // And from B3 to reverse_iterator<B4>.
+  static_assert( ! std::is_nothrow_constructible<R4, const B4&>(), "" );
+  static_assert( ! std::is_nothrow_constructible<R4, B4>(), "" );
+  static_assert( ! std::is_nothrow_constructible<R4, const B3&>(), "" );
+  static_assert( ! std::is_nothrow_constructible<R4, B3>(), "" );
+
+  static_assert( noexcept(std::declval<R1&>().base()), "" );
+  static_assert( ! noexcept(std::declval<R3&>().base()), "" );
+}