This is the mail archive of the libstdc++@gcc.gnu.org mailing list for the libstdc++ project.
| Index Nav: | [Date Index] [Subject Index] [Author Index] [Thread Index] | |
|---|---|---|
| Message Nav: | [Date Prev] [Date Next] | [Thread Prev] [Thread Next] |
| Other format: | [Raw text] | |
On 02/05/2018 13:49, Jonathan Wakely wrote:
Yes, it helps to make sure rb tree code is correct and it avoids usage of non-standard _S_always_equal. We could even use std::allocator_traits in C++11.On 01/05/18 21:56 +0200, François Dumont wrote:Hi If not told otherwise I'll commit attached patch tomorrow.Please do not commit it, see below.Already discussed here: https://gcc.gnu.org/ml/libstdc++/2017-10/msg00053.htmlThere's no changelog entry with the patch, so to recap, the changes are: - noexcept specifications are automatically deduced instead of being stated explicitly.
Yes, I realized it after I wrote the tests to validate the noexcept qualification.- 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.
- 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?
Yes, this is consistent with most of the other containers doing the same.
Deducing the noexcept specifications probably has a (small?) impact on compilation speed, as currently the allocator-extended move constructors have: - noexcept(is_nothrow_copy_constructible<_Compare>::value - && _Alloc_traits::_S_always_equal()) After the patch they have to determine the noexcept-ness of the _Rep_type constructor taking allocator_type, which has to determine the noexcept-ness of the _Rep_type constructor taking a _Node_allocator, which has to determine the noexcept-ness of the constructor it dispatches to depending on _S_always_equal(), which is simply: + noexcept(is_nothrow_move_constructible<_Rb_tree_impl<_Compare>>::value) So instead of just using that value in the first place we have to perform three rounds of overload resolution to find the right constructor and then check its exception specification. And then we get the wrong answer (see below). 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 must confess that I didn't consider this aspect of the patch. I just wanted the code to be more logical. I even wonder why it wasn't done this way, now I know. Could this compilation cost be testable ? Isn't the compiler doing this work only if the constructor is being used ? Or even just only if it needs to know about the noexcept qualification ?
Has std::declval<>() been modified recently ? I think I tried that and it was working but after a while it started to fail so I had to replace with the other expression. I'll try it again if I eventually commit this.Françoisdiff --git a/libstdc++-v3/include/bits/stl_map.h b/libstdc++-v3/include/bits/stl_map.hindex 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>())) )
In my copy of the Standard I see no noexcept qualifications on associative container constructors, is it too old ?: _M_t(std::move(__m._M_t), _Pair_alloc_type(__a)) { } /// Allocator-extended initialier-list constructor.diff --git a/libstdc++-v3/include/bits/stl_multimap.h b/libstdc++-v3/include/bits/stl_multimap.hindex fc8f454..b9289ab 100644 --- a/libstdc++-v3/include/bits/stl_multimap.h +++ b/libstdc++-v3/include/bits/stl_multimap.h @@ -237,8 +237,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER /// Allocator-extended move constructor. multimap(multimap&& __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>())) ) : _M_t(std::move(__m._M_t), _Pair_alloc_type(__a)) { } /// Allocator-extended initialier-list constructor.diff --git a/libstdc++-v3/include/bits/stl_multiset.h b/libstdc++-v3/include/bits/stl_multiset.hindex f41f56c..afaf3b6 100644 --- a/libstdc++-v3/include/bits/stl_multiset.h +++ b/libstdc++-v3/include/bits/stl_multiset.h @@ -253,8 +253,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER /// Allocator-extended move constructor. multiset(multiset&& __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<_Key_alloc_type>())) ) : _M_t(std::move(__m._M_t), _Key_alloc_type(__a)) { } /// Allocator-extended initialier-list constructor.diff --git a/libstdc++-v3/include/bits/stl_set.h b/libstdc++-v3/include/bits/stl_set.hindex 2e332ef..fc6d1f7 100644 --- a/libstdc++-v3/include/bits/stl_set.h +++ b/libstdc++-v3/include/bits/stl_set.h @@ -257,8 +257,8 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER /// Allocator-extended move constructor. set(set&& __x, const allocator_type& __a) - noexcept(is_nothrow_copy_constructible<_Compare>::value - && _Alloc_traits::_S_always_equal()) + noexcept( noexcept( + _Rep_type(std::move(__x._M_t), declval<_Key_alloc_type>())) ) : _M_t(std::move(__x._M_t), _Key_alloc_type(__a)) { } /// Allocator-extended initialier-list constructor.diff --git a/libstdc++-v3/include/bits/stl_tree.h b/libstdc++-v3/include/bits/stl_tree.hindex d0a8448..732ac55 100644 --- a/libstdc++-v3/include/bits/stl_tree.h +++ b/libstdc++-v3/include/bits/stl_tree.h @@ -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) { } @@ -955,10 +961,32 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _Rb_tree(_Rb_tree&&) = default; _Rb_tree(_Rb_tree&& __x, const allocator_type& __a) + noexcept( noexcept( + _Rb_tree(std::move(__x), declval<_Node_allocator>())) ) : _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_move_constructible<_Rb_tree_impl<_Compare>>::value)This is wrong. The result of is_nothrow_move_constructible<_Rb_tree_impl<_Compare>> depends on this constructor: _Rb_tree_impl(_Rb_tree_impl&&) = default; That constructor depends on the allocator type, which might not be noexcept, but it's undefined behaviour for it to throw. We don't want the exception-specification to depend on the allocator, otherwise we fail this test on the line marked XXX: template<typename T> struct Alloc : std::allocator<T> { Alloc() { } Alloc(const Alloc&) { } template<typename U> Alloc(const Alloc<U>&) { } template<typename U> struct rebind { using other = Alloc<U>; }; }; template<typename A> using Set = std::set<int, std::less<int>, A>; template<typename A> using Test = std::is_nothrow_constructible<Set<A>, Set<A>, A>; int main() { static_assert(Test<std::allocator<int>>::value, ""); static_assert(Test<Alloc<int>>::value, ""); // XXX }
The changes to deduce the exception-specifications seem to make the code more fragile -- do we really want to make those changes?
Considering all your feedbacks I would say no. I'll rework it then.
+ : _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::move(__x), std::move(__a), + declval<typename _Alloc_traits::is_always_equal>()) ) ) + : _Rb_tree(std::move(__x), std::move(__a), + typename _Alloc_traits::is_always_equal{}) + { } #endifdiff --git a/libstdc++-v3/include/debug/map.h b/libstdc++-v3/include/debug/map.hindex 414b4dc..9edb7dc 100644 --- a/libstdc++-v3/include/debug/map.h +++ b/libstdc++-v3/include/debug/map.h @@ -105,8 +105,10 @@ 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) { } + _Base(std::move(__m._M_base()), __a) + { }This part of the patch is actually a bug fix, making the debug mode consistent with normal mode.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.ccindex 0041408..8791eb8 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( noexcept( mtype(std::declval<mtype>(), + std::declval<const typename mtype::allocator_type&>()) ),This can use std::is_nothrow_constructible+ "noexcept move constructor with allocator" ); + +struct ExceptLessPlease name this "ThrowingLess" instead of "ExceptLess", I think that's more descriptive.+{ + ExceptLess() = default; + ExceptLess(const ExceptLess&) /* noexcept */ + { } + + bool + operator()(int l, int r) const + { return l < r; } +}; + +typedef std::map<int, int, ExceptLess> emtype; + +static_assert( !noexcept( emtype(std::declval<emtype>(), + std::declval<const typename emtype::allocator_type&>()) ),This can use std::is_nothrow_constructible too. Same comments for the other tests
Ok
| Index Nav: | [Date Index] [Subject Index] [Author Index] [Thread Index] | |
|---|---|---|
| Message Nav: | [Date Prev] [Date Next] | [Thread Prev] [Thread Next] |