Here is the rework of this patch.
On 02/05/2018 13:49, Jonathan Wakely wrote:
There's no changelog entry with the patch, so to recap, the changes
are:
- noexcept specifications are automatically deduced instead of being
stated explicitly.
I simplified this part, it is not deduced anymore except for Debug mode
relying on Normal mode qualifications.
- The allocator-extended move constructors get correct exception
specifications in Debug Mode (consistent with normal mode). This
should be done anyway, independent of any other changes.
I kept this untouch.
- We avoid a `if (__x._M_root() != nullptr)` branch in the case where
the allocator-extended move constructor is used with an always-equal
allocator, right?
Kept too.
Compilation time for containers is already **much** slower since the
allocator-extended constructors were added for GCC 5.x, despite very
few people ever using those constructors. This patch seems to require
even more work from the compiler to parse constructors nobody uses.
I restore direct qualifications.
diff --git a/libstdc++-v3/include/bits/stl_map.h
b/libstdc++-v3/include/bits/stl_map.h
index a4a026e..2b8fd27 100644
--- a/libstdc++-v3/include/bits/stl_map.h
+++ b/libstdc++-v3/include/bits/stl_map.h
@@ -240,8 +240,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER
/// Allocator-extended move constructor.
map(map&& __m, const allocator_type& __a)
- noexcept(is_nothrow_copy_constructible<_Compare>::value
- && _Alloc_traits::_S_always_equal())
+ noexcept( noexcept(
+ _Rep_type(std::move(__m._M_t), declval<_Pair_alloc_type>())) )
All these calls to declval need to be qualified to avoid ADL.
It seems strange to have a mix of real values and declval expressions,
rather than:
_Rep_type(std::declval<_Rep_type>(),
std::declval<_Pair_alloc_type>())) )
I add std:: qualitification and generalized usage.
- _Rb_tree(_Rb_tree&& __x, _Node_allocator&& __a);
+ private:
+ _Rb_tree(_Rb_tree&& __x, _Node_allocator&& __a, true_type)
+ noexcept(is_nothrow_move_constructible<_Rb_tree_impl<_Compare>>::value)
This is wrong.
As stated previously I removed this but based on this remark I also
change the _Rb_tree_impl() qualification so that it doesn't depend
anymore on allocator default constructor qualification.
I've added a test for that too.
This can use std::is_nothrow_constructible
Indeed.
+ "noexcept move constructor with allocator" );
+
+struct ExceptLess
Please name this "ThrowingLess" instead of "ExceptLess", I think
that's more descriptive.
I prefered "not_noexcept" prefix cause those operators are not throwing
anything neither.
Tested under Linux x86_64, ok to commit ?
* include/bits/stl_tree.h
(_Rb_tree_impl()):
Remove is_nothrow_default_constructible<_Node_allocator> from noexcept
qualification.
(_Rb_tree_impl(_Rb_tree_impl&&, _Node_allocator&&)): New.
(_Rb_tree(_Rb_tree&&, _Node_allocator&&, std::true_type)): New, use
latter.
(_Rb_tree(_Rb_tree&&, _Node_allocator&&, std::false_type)): New.
(_Rb_tree(_Rb_tree&&, _Node_allocator&&)): Adapt, use latters.
* include/debug/map.h
(map(map&&, const_allocator_type&)): Add noexcept qualitication.
* include/debug/multimap.h
(multimap(multimap&&, const_allocator_type&)): Add noexcept
qualification.
* include/debug/set.h
(set(set&&, const_allocator_type&)): Add noexcept qualitication.
* include/debug/multiset.h
(multiset(multiset&&, const_allocator_type&)): Add noexcept
qualification.
* testsuite/23_containers/map/cons/noexcept_default_construct.cc:
Add checks.
* testsuite/23_containers/map/cons/noexcept_move_construct.cc:
Add checks.
* testsuite/23_containers/multimap/cons/noexcept_default_construct.cc:
Add checks.
* testsuite/23_containers/multimap/cons/noexcept_move_construct.cc:
Add checks.
* testsuite/23_containers/multiset/cons/noexcept_default_construct.cc:
Add checks.
* testsuite/23_containers/multiset/cons/noexcept_move_construct.cc:
Add checks.
* testsuite/23_containers/set/cons/noexcept_default_construct.cc:
Add checks.
* testsuite/23_containers/set/cons/noexcept_move_construct.cc:
Add checks.
François
diff --git a/libstdc++-v3/include/bits/stl_tree.h b/libstdc++-v3/include/bits/stl_tree.h
index d0a8448..9e59259 100644
--- a/libstdc++-v3/include/bits/stl_tree.h
+++ b/libstdc++-v3/include/bits/stl_tree.h
@@ -698,8 +698,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
_Rb_tree_impl()
_GLIBCXX_NOEXCEPT_IF(
- is_nothrow_default_constructible<_Node_allocator>::value
- && is_nothrow_default_constructible<_Base_key_compare>::value )
+ // is_nothrow_default_constructible<_Node_allocator>::value &&
+ is_nothrow_default_constructible<_Base_key_compare>::value )
: _Node_allocator()
{ }
@@ -715,6 +715,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
#else
_Rb_tree_impl(_Rb_tree_impl&&) = default;
+ _Rb_tree_impl(_Rb_tree_impl&& __x, _Node_allocator&& __a)
+ : _Node_allocator(std::move(__a)),
+ _Base_key_compare(std::move(__x)),
+ _Rb_tree_header(std::move(__x))
+ { }
+
_Rb_tree_impl(const _Key_compare& __comp, _Node_allocator&& __a)
: _Node_allocator(std::move(__a)), _Base_key_compare(__comp)
{ }
@@ -958,7 +964,27 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
: _Rb_tree(std::move(__x), _Node_allocator(__a))
{ }
- _Rb_tree(_Rb_tree&& __x, _Node_allocator&& __a);
+ private:
+ _Rb_tree(_Rb_tree&& __x, _Node_allocator&& __a, true_type)
+ noexcept(is_nothrow_default_constructible<_Compare>::value)
+ : _M_impl(std::move(__x._M_impl), std::move(__a))
+ { }
+
+ _Rb_tree(_Rb_tree&& __x, _Node_allocator&& __a, false_type)
+ : _M_impl(__x._M_impl._M_key_compare, std::move(__a))
+ {
+ if (__x._M_root() != nullptr)
+ _M_move_data(__x, false_type{});
+ }
+
+ public:
+ _Rb_tree(_Rb_tree&& __x, _Node_allocator&& __a)
+ noexcept( noexcept(
+ _Rb_tree(std::declval<_Rb_tree>(), std::declval<_Node_allocator>(),
+ std::declval<typename _Alloc_traits::is_always_equal>())) )
+ : _Rb_tree(std::move(__x), std::move(__a),
+ typename _Alloc_traits::is_always_equal{})
+ { }
#endif
~_Rb_tree() _GLIBCXX_NOEXCEPT
@@ -1358,22 +1384,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
private:
// Move elements from container with equal allocator.
void
- _M_move_data(_Rb_tree& __x, std::true_type)
+ _M_move_data(_Rb_tree& __x, true_type)
{ _M_impl._M_move_data(__x._M_impl); }
// Move elements from container with possibly non-equal allocator,
// which might result in a copy not a move.
void
- _M_move_data(_Rb_tree&, std::false_type);
+ _M_move_data(_Rb_tree&, false_type);
// Move assignment from container with equal allocator.
void
- _M_move_assign(_Rb_tree&, std::true_type);
+ _M_move_assign(_Rb_tree&, true_type);
// Move assignment from container with possibly non-equal allocator,
// which might result in a copy not a move.
void
- _M_move_assign(_Rb_tree&, std::false_type);
+ _M_move_assign(_Rb_tree&, false_type);
#endif
#if __cplusplus > 201402L
@@ -1601,23 +1627,12 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
#if __cplusplus >= 201103L
template<typename _Key, typename _Val, typename _KeyOfValue,
typename _Compare, typename _Alloc>
- _Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
- _Rb_tree(_Rb_tree&& __x, _Node_allocator&& __a)
- : _M_impl(__x._M_impl._M_key_compare, std::move(__a))
- {
- using __eq = typename _Alloc_traits::is_always_equal;
- if (__x._M_root() != nullptr)
- _M_move_data(__x, __eq());
- }
-
- template<typename _Key, typename _Val, typename _KeyOfValue,
- typename _Compare, typename _Alloc>
void
_Rb_tree<_Key, _Val, _KeyOfValue, _Compare, _Alloc>::
- _M_move_data(_Rb_tree& __x, std::false_type)
+ _M_move_data(_Rb_tree& __x, false_type)
{
if (_M_get_Node_allocator() == __x._M_get_Node_allocator())
- _M_move_data(__x, std::true_type());
+ _M_move_data(__x, true_type());
else
{
_Alloc_node __an(*this);
@@ -1639,7 +1654,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
{
clear();
if (__x._M_root() != nullptr)
- _M_move_data(__x, std::true_type());
+ _M_move_data(__x, true_type());
std::__alloc_on_move(_M_get_Node_allocator(),
__x._M_get_Node_allocator());
}
diff --git a/libstdc++-v3/include/debug/map.h b/libstdc++-v3/include/debug/map.h
index 414b4dc..7a4e2bf 100644
--- a/libstdc++-v3/include/debug/map.h
+++ b/libstdc++-v3/include/debug/map.h
@@ -105,6 +105,7 @@ namespace __debug
: _Base(__m, __a) { }
map(map&& __m, const allocator_type& __a)
+ noexcept( noexcept(_Base(std::move(__m._M_base()), __a)) )
: _Safe(std::move(__m._M_safe()), __a),
_Base(std::move(__m._M_base()), __a) { }
diff --git a/libstdc++-v3/include/debug/multimap.h b/libstdc++-v3/include/debug/multimap.h
index 8d6358b..9ee3809 100644
--- a/libstdc++-v3/include/debug/multimap.h
+++ b/libstdc++-v3/include/debug/multimap.h
@@ -105,6 +105,7 @@ namespace __debug
: _Base(__m, __a) { }
multimap(multimap&& __m, const allocator_type& __a)
+ noexcept( noexcept(_Base(std::move(__m._M_base()), __a)) )
: _Safe(std::move(__m._M_safe()), __a),
_Base(std::move(__m._M_base()), __a) { }
diff --git a/libstdc++-v3/include/debug/multiset.h b/libstdc++-v3/include/debug/multiset.h
index 4e1406e..0be14d7 100644
--- a/libstdc++-v3/include/debug/multiset.h
+++ b/libstdc++-v3/include/debug/multiset.h
@@ -104,6 +104,7 @@ namespace __debug
: _Base(__m, __a) { }
multiset(multiset&& __m, const allocator_type& __a)
+ noexcept( noexcept(_Base(std::move(__m._M_base()), __a)) )
: _Safe(std::move(__m._M_safe()), __a),
_Base(std::move(__m._M_base()), __a) { }
diff --git a/libstdc++-v3/include/debug/set.h b/libstdc++-v3/include/debug/set.h
index a886860..7f24a83 100644
--- a/libstdc++-v3/include/debug/set.h
+++ b/libstdc++-v3/include/debug/set.h
@@ -104,6 +104,7 @@ namespace __debug
: _Base(__x, __a) { }
set(set&& __x, const allocator_type& __a)
+ noexcept( noexcept(_Base(std::move(__x._M_base()), __a)) )
: _Safe(std::move(__x._M_safe()), __a),
_Base(std::move(__x._M_base()), __a) { }
diff --git a/libstdc++-v3/testsuite/23_containers/map/cons/noexcept_default_construct.cc b/libstdc++-v3/testsuite/23_containers/map/cons/noexcept_default_construct.cc
index 4b9e119..7658916 100644
--- a/libstdc++-v3/testsuite/23_containers/map/cons/noexcept_default_construct.cc
+++ b/libstdc++-v3/testsuite/23_containers/map/cons/noexcept_default_construct.cc
@@ -30,3 +30,19 @@ struct cmp
using mtype2 = std::map<int, int, cmp>;
static_assert( !std::is_nothrow_default_constructible<mtype2>::value, "Error");
+
+template<typename _Tp>
+ struct not_noexcept_cons_alloc : std::allocator<_Tp>
+ {
+ not_noexcept_cons_alloc() /* noexcept */
+ { }
+
+ template<typename _Tp1>
+ struct rebind
+ { typedef not_noexcept_cons_alloc<_Tp1> other; };
+ };
+
+using mtype3 = std::map<int, int, std::less<int>,
+ not_noexcept_cons_alloc<std::pair<const int, int>>>;
+
+static_assert(std::is_nothrow_default_constructible<mtype3>::value, "Error");
diff --git a/libstdc++-v3/testsuite/23_containers/map/cons/noexcept_move_construct.cc b/libstdc++-v3/testsuite/23_containers/map/cons/noexcept_move_construct.cc
index 0041408..723a63f 100644
--- a/libstdc++-v3/testsuite/23_containers/map/cons/noexcept_move_construct.cc
+++ b/libstdc++-v3/testsuite/23_containers/map/cons/noexcept_move_construct.cc
@@ -23,4 +23,25 @@
typedef std::map<int, int> mtype;
-static_assert(std::is_nothrow_move_constructible<mtype>::value, "Error");
+static_assert( std::is_nothrow_move_constructible<mtype>::value,
+ "noexcept move constructor" );
+static_assert( std::is_nothrow_constructible<mtype,
+ mtype&&, const typename mtype::allocator_type&>::value,
+ "noexcept move constructor with allocator" );
+
+struct not_noexcept_less
+{
+ not_noexcept_less() = default;
+ not_noexcept_less(const not_noexcept_less&) /* noexcept */
+ { }
+
+ bool
+ operator()(int l, int r) const
+ { return l < r; }
+};
+
+typedef std::map<int, int, not_noexcept_less> emtype;
+
+static_assert( !std::is_nothrow_constructible<emtype, emtype&&,
+ const typename emtype::allocator_type&>::value,
+ "except move constructor with allocator" );
diff --git a/libstdc++-v3/testsuite/23_containers/multimap/cons/noexcept_default_construct.cc b/libstdc++-v3/testsuite/23_containers/multimap/cons/noexcept_default_construct.cc
index 9873770..e83cd87 100644
--- a/libstdc++-v3/testsuite/23_containers/multimap/cons/noexcept_default_construct.cc
+++ b/libstdc++-v3/testsuite/23_containers/multimap/cons/noexcept_default_construct.cc
@@ -30,3 +30,19 @@ struct cmp
using mtype2 = std::multimap<int, int, cmp>;
static_assert( !std::is_nothrow_default_constructible<mtype2>::value, "Error");
+
+template<typename _Tp>
+ struct not_noexcept_cons_alloc : std::allocator<_Tp>
+ {
+ not_noexcept_cons_alloc() /* noexcept */
+ { }
+
+ template<typename _Tp1>
+ struct rebind
+ { typedef not_noexcept_cons_alloc<_Tp1> other; };
+ };
+
+using mtype3 = std::multimap<int, int, std::less<int>,
+ not_noexcept_cons_alloc<std::pair<const int, int>>>;
+
+static_assert(std::is_nothrow_default_constructible<mtype3>::value, "Error");
diff --git a/libstdc++-v3/testsuite/23_containers/multimap/cons/noexcept_move_construct.cc b/libstdc++-v3/testsuite/23_containers/multimap/cons/noexcept_move_construct.cc
index 0319a61..1612b0d 100644
--- a/libstdc++-v3/testsuite/23_containers/multimap/cons/noexcept_move_construct.cc
+++ b/libstdc++-v3/testsuite/23_containers/multimap/cons/noexcept_move_construct.cc
@@ -23,4 +23,25 @@
typedef std::multimap<int, int> mmtype;
-static_assert(std::is_nothrow_move_constructible<mmtype>::value, "Error");
+static_assert( std::is_nothrow_move_constructible<mmtype>::value,
+ "noexcept move constructor" );
+static_assert( std::is_nothrow_constructible<mmtype,
+ mmtype&&, const typename mmtype::allocator_type&>::value,
+ "noexcept move constructor with allocator" );
+
+struct not_noexcept_less
+{
+ not_noexcept_less() = default;
+ not_noexcept_less(const not_noexcept_less&) /* noexcept */
+ { }
+
+ bool
+ operator()(int l, int r) const
+ { return l < r; }
+};
+
+typedef std::multimap<int, int, not_noexcept_less> emmtype;
+
+static_assert( !std::is_nothrow_constructible<emmtype, emmtype&&,
+ const typename emmtype::allocator_type&>::value,
+ "except move constructor with allocator" );
diff --git a/libstdc++-v3/testsuite/23_containers/multiset/cons/noexcept_default_construct.cc b/libstdc++-v3/testsuite/23_containers/multiset/cons/noexcept_default_construct.cc
index 963e3d1..4c465f0 100644
--- a/libstdc++-v3/testsuite/23_containers/multiset/cons/noexcept_default_construct.cc
+++ b/libstdc++-v3/testsuite/23_containers/multiset/cons/noexcept_default_construct.cc
@@ -30,3 +30,19 @@ struct cmp
using stype2 = std::multiset<int, cmp>;
static_assert( !std::is_nothrow_default_constructible<stype2>::value, "Error");
+
+template<typename _Tp>
+ struct not_noexcept_cons_alloc : std::allocator<_Tp>
+ {
+ not_noexcept_cons_alloc() /* noexcept */
+ { }
+
+ template<typename _Tp1>
+ struct rebind
+ { typedef not_noexcept_cons_alloc<_Tp1> other; };
+ };
+
+using stype3 = std::multiset<int, std::less<int>,
+ not_noexcept_cons_alloc<int>>;
+
+static_assert(std::is_nothrow_default_constructible<stype3>::value, "Error");
diff --git a/libstdc++-v3/testsuite/23_containers/multiset/cons/noexcept_move_construct.cc b/libstdc++-v3/testsuite/23_containers/multiset/cons/noexcept_move_construct.cc
index b1c6dd5..4ef5127 100644
--- a/libstdc++-v3/testsuite/23_containers/multiset/cons/noexcept_move_construct.cc
+++ b/libstdc++-v3/testsuite/23_containers/multiset/cons/noexcept_move_construct.cc
@@ -23,4 +23,25 @@
typedef std::multiset<int> mstype;
-static_assert(std::is_nothrow_move_constructible<mstype>::value, "Error");
+static_assert( std::is_nothrow_move_constructible<mstype>::value,
+ "noexcept move constructor" );
+static_assert( std::is_nothrow_constructible<mstype,
+ mstype&&, const typename mstype::allocator_type&>::value,
+ "noexcept move constructor with allocator" );
+
+struct not_noexcept_less
+{
+ not_noexcept_less() = default;
+ not_noexcept_less(const not_noexcept_less&) /* noexcept */
+ { }
+
+ bool
+ operator()(int l, int r) const
+ { return l < r; }
+};
+
+typedef std::multiset<int, not_noexcept_less> emstype;
+
+static_assert( !std::is_nothrow_constructible<emstype, emstype&&,
+ const typename emstype::allocator_type&>::value,
+ "except move constructor with allocator" );
diff --git a/libstdc++-v3/testsuite/23_containers/set/cons/noexcept_default_construct.cc b/libstdc++-v3/testsuite/23_containers/set/cons/noexcept_default_construct.cc
index 882a12c..51a3db5 100644
--- a/libstdc++-v3/testsuite/23_containers/set/cons/noexcept_default_construct.cc
+++ b/libstdc++-v3/testsuite/23_containers/set/cons/noexcept_default_construct.cc
@@ -30,3 +30,19 @@ struct cmp
using stype2 = std::set<int, cmp>;
static_assert( !std::is_nothrow_default_constructible<stype2>::value, "Error");
+
+template<typename _Tp>
+ struct not_noexcept_cons_alloc : std::allocator<_Tp>
+ {
+ not_noexcept_cons_alloc() /* noexcept */
+ { }
+
+ template<typename _Tp1>
+ struct rebind
+ { typedef not_noexcept_cons_alloc<_Tp1> other; };
+ };
+
+using stype3 = std::set<int, std::less<int>,
+ not_noexcept_cons_alloc<int>>;
+
+static_assert(std::is_nothrow_default_constructible<stype3>::value, "Error");
diff --git a/libstdc++-v3/testsuite/23_containers/set/cons/noexcept_move_construct.cc b/libstdc++-v3/testsuite/23_containers/set/cons/noexcept_move_construct.cc
index a7de0ef..942400e 100644
--- a/libstdc++-v3/testsuite/23_containers/set/cons/noexcept_move_construct.cc
+++ b/libstdc++-v3/testsuite/23_containers/set/cons/noexcept_move_construct.cc
@@ -23,4 +23,25 @@
typedef std::set<int> stype;
-static_assert(std::is_nothrow_move_constructible<stype>::value, "Error");
+static_assert( std::is_nothrow_move_constructible<stype>::value,
+ "noexcept move constructor" );
+static_assert( std::is_nothrow_constructible<stype,
+ stype&&, const typename stype::allocator_type&>::value,
+ "noexcept move constructor with allocator" );
+
+struct not_noexcept_less
+{
+ not_noexcept_less() = default;
+ not_noexcept_less(const not_noexcept_less&) /* noexcept */
+ { }
+
+ bool
+ operator()(int l, int r) const
+ { return l < r; }
+};
+
+typedef std::set<int, not_noexcept_less> estype;
+
+static_assert( !std::is_nothrow_constructible<estype, estype&&,
+ const typename estype::allocator_type&>::value,
+ "except move constructor with allocator" );