libstdc++: Implement LWG 4016 for std::ranges::to

This implements the proposed resolution of LWG 4016, so that
std::ranges::to does not use std::back_inserter and std::inserter.
Instead it inserts at the back of the container directly, using
the first supported one of emplace_back, push_back, emplace, and insert.

Using emplace avoids creating a temporary that has to be moved into the
container, for cases where the source range and the destination
container do not have the same value type.

libstdc++-v3/ChangeLog:

	* include/std/ranges (__detail::__container_insertable): Remove.
	(__detail::__container_inserter): Remove.
	(ranges::to): Use emplace_back or emplace, as per LWG 4016.
	* testsuite/std/ranges/conv/1.cc (Cont4, test_2_1_4): Check for
	use of emplace_back and emplace.
This commit is contained in:
Jonathan Wakely 2023-11-26 21:32:35 +00:00
parent 5e8a30d8b8
commit 45630fbcf7
2 changed files with 144 additions and 55 deletions

View file

@ -9229,26 +9229,6 @@ namespace __detail
{ __c.max_size() } -> same_as<decltype(__n)>;
};
template<typename _Container, typename _Ref>
constexpr bool __container_insertable
= requires(_Container& __c, _Ref&& __ref) {
typename _Container::value_type;
requires (
requires { __c.push_back(std::forward<_Ref>(__ref)); }
|| requires { __c.insert(__c.end(), std::forward<_Ref>(__ref)); }
);
};
template<typename _Ref, typename _Container>
constexpr auto
__container_inserter(_Container& __c)
{
if constexpr (requires { __c.push_back(std::declval<_Ref>()); })
return std::back_inserter(__c);
else
return std::inserter(__c, __c.end());
}
template<typename _Cont, typename _Range>
constexpr bool __toable = requires {
requires (!input_range<_Cont>
@ -9301,17 +9281,33 @@ namespace __detail
std::forward<_Args>(__args)...);
else
{
using __detail::__container_insertable;
using __detail::__reservable_container;
using _RefT = range_reference_t<_Rg>;
static_assert(constructible_from<_Cont, _Args...>);
static_assert(__container_insertable<_Cont, _RefT>);
_Cont __c(std::forward<_Args>(__args)...);
if constexpr (sized_range<_Rg> && __reservable_container<_Cont>)
if constexpr (sized_range<_Rg>
&& __detail::__reservable_container<_Cont>)
__c.reserve(static_cast<range_size_t<_Cont>>(ranges::size(__r)));
auto __ins = __detail::__container_inserter<_RefT>(__c);
for (auto&& __e : __r)
*__ins++ = std::forward<decltype(__e)>(__e);
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 4016. container-insertable checks do not match what
// container-inserter does
auto __it = ranges::begin(__r);
const auto __sent = ranges::end(__r);
while (__it != __sent)
{
if constexpr (requires { __c.emplace_back(*__it); })
__c.emplace_back(*__it);
else if constexpr (requires { __c.push_back(*__it); })
__c.push_back(*__it);
else
{
auto __end = __c.end();
if constexpr (requires { __c.emplace(__end, *__it); })
__end = __c.emplace(__end, *__it);
else
__end = __c.insert(__end, *__it);
}
++__it;
}
return __c;
}
}

View file

@ -203,33 +203,51 @@ test_2_1_3()
VERIFY( c2.c.get_allocator() == Alloc(78) );
}
template<typename C, bool UsePushBack = true>
enum AppendKind { None, EmplaceBack, PushBack, Emplace, Insert };
template<typename C, AppendKind Kind>
struct Cont4
{
using value_type = typename C::value_type;
// Only support construction with no args or an allocator.
// This forces the use of either push_back or insert to fill the container.
// This forces the use of either emplace_back, push_back, emplace or insert.
Cont4() { }
Cont4(typename C::allocator_type a) : c(a) { }
// Satisfying container-insertable requires either this ...
template<typename T>
requires UsePushBack
requires (Kind <= EmplaceBack)
&& requires(C& c, T&& t) { c.emplace_back(std::forward<T>(t)); }
void
emplace_back(T&& t)
{
kind = EmplaceBack;
c.emplace_back(std::forward<T>(t));
}
template<typename T>
requires (Kind <= PushBack)
&& requires(C& c, T&& t) { c.push_back(std::forward<T>(t)); }
void
push_back(T&& t)
{
kind = PushBack;
c.push_back(std::forward<T>(t));
used_push_back = true;
}
// ... or this:
template<typename T>
requires (Kind <= Emplace)
&& requires(C& c, T&& t) { c.emplace(c.end(), std::forward<T>(t)); }
typename C::iterator
emplace(typename C::iterator, T&& t)
{
kind = Emplace;
return c.emplace(c.end(), std::forward<T>(t));
}
template<typename T>
typename C::iterator
insert(typename C::iterator, T&& t)
{
used_push_back = false;
kind = Insert;
return c.insert(c.end(), std::forward<T>(t));
}
@ -254,7 +272,7 @@ struct Cont4
auto max_size() const { return c.max_size(); }
C c;
bool used_push_back = false;
AppendKind kind{};
bool used_reserve = false;
};
@ -265,38 +283,113 @@ test_2_1_4()
// container-insertable<C, range_reference_t<R>>
using Alloc = __gnu_test::uneq_allocator<int>;
using Alloc2 = __gnu_test::uneq_allocator<short>;
using V = std::vector<int, Alloc>;
using List = std::list<short, Alloc2>;
std::list<unsigned> l{1u, 2u, 3u};
auto c = std::ranges::to<Cont4<V>>(l);
static_assert(std::is_same_v<decltype(c), Cont4<V>>);
std::list<long> l2{4l, 5l, 6l};
// use vector::emplace_back and vector::reserve
auto c = std::ranges::to<Cont4<V, EmplaceBack>>(l);
static_assert(std::is_same_v<decltype(c), Cont4<V, EmplaceBack>>);
VERIFY( c.c == V(l.begin(), l.end()) );
VERIFY( c.used_push_back );
VERIFY( c.kind == EmplaceBack );
VERIFY( c.used_reserve );
std::list<long> l2{4l, 5l, 6l};
auto c2 = std::ranges::to<Cont4<V>>(l2, Alloc(78));
static_assert(std::is_same_v<decltype(c2), Cont4<V>>);
// use vector::emplace_back and vector::reserve
auto c2 = std::ranges::to<Cont4<V, EmplaceBack>>(l2, Alloc(78));
static_assert(std::is_same_v<decltype(c2), Cont4<V, EmplaceBack>>);
VERIFY( c2.c == V(l2.begin(), l2.end()) );
VERIFY( c2.c.get_allocator() == Alloc(78) );
VERIFY( c2.used_push_back );
VERIFY( c2.kind == EmplaceBack );
VERIFY( c2.used_reserve );
using Alloc2 = __gnu_test::uneq_allocator<short>;
using List = std::list<short, Alloc2>;
auto c3 = std::ranges::to<Cont4<List>>(c.c, Alloc2(99));
static_assert(std::is_same_v<decltype(c3), Cont4<List>>);
// use list::emplace_back
auto c3 = std::ranges::to<Cont4<List, EmplaceBack>>(c.c, Alloc2(99));
static_assert(std::is_same_v<decltype(c3), Cont4<List, EmplaceBack>>);
VERIFY( c3.c == List(l.begin(), l.end()) );
VERIFY( c3.c.get_allocator() == Alloc(99) );
VERIFY( c3.used_push_back );
VERIFY( c3.kind == EmplaceBack );
VERIFY( ! c3.used_reserve );
auto c4 = std::ranges::to<Cont4<List, false>>(c.c, Alloc2(111));
static_assert(std::is_same_v<decltype(c4), Cont4<List, false>>);
VERIFY( c4.c == List(l.begin(), l.end()) );
VERIFY( c4.c.get_allocator() == Alloc(111) );
VERIFY( ! c4.used_push_back );
VERIFY( ! c4.used_reserve );
// use vector::push_back and vector::reserve
auto c4 = std::ranges::to<Cont4<V, PushBack>>(l);
static_assert(std::is_same_v<decltype(c4), Cont4<V, PushBack>>);
VERIFY( c4.c == V(l.begin(), l.end()) );
VERIFY( c4.kind == PushBack );
VERIFY( c4.used_reserve );
// use vector::push_back and vector::reserve
auto c5 = std::ranges::to<Cont4<V, PushBack>>(l2, Alloc(78));
static_assert(std::is_same_v<decltype(c5), Cont4<V, PushBack>>);
VERIFY( c5.c == V(l2.begin(), l2.end()) );
VERIFY( c5.c.get_allocator() == Alloc(78) );
VERIFY( c5.kind == PushBack );
VERIFY( c5.used_reserve );
// use list::push_back
auto c6 = std::ranges::to<Cont4<List, PushBack>>(c.c, Alloc2(99));
static_assert(std::is_same_v<decltype(c6), Cont4<List, PushBack>>);
VERIFY( c6.c == List(l.begin(), l.end()) );
VERIFY( c6.c.get_allocator() == Alloc(99) );
VERIFY( c6.kind == PushBack );
VERIFY( ! c6.used_reserve );
// use vector::emplace and vector::reserve
auto c7 = std::ranges::to<Cont4<V, Emplace>>(l);
static_assert(std::is_same_v<decltype(c7), Cont4<V, Emplace>>);
VERIFY( c7.c == V(l.begin(), l.end()) );
VERIFY( c7.kind == Emplace );
VERIFY( c7.used_reserve );
// use vector::emplace and vector::reserve
auto c8 = std::ranges::to<Cont4<V, Emplace>>(l2, Alloc(78));
static_assert(std::is_same_v<decltype(c8), Cont4<V, Emplace>>);
VERIFY( c8.c == V(l2.begin(), l2.end()) );
VERIFY( c8.c.get_allocator() == Alloc(78) );
VERIFY( c8.kind == Emplace );
VERIFY( c8.used_reserve );
// use list::emplace
auto c9 = std::ranges::to<Cont4<List, Emplace>>(c.c, Alloc2(99));
static_assert(std::is_same_v<decltype(c9), Cont4<List, Emplace>>);
VERIFY( c9.c == List(l.begin(), l.end()) );
VERIFY( c9.c.get_allocator() == Alloc(99) );
VERIFY( c9.kind == Emplace );
VERIFY( ! c9.used_reserve );
// use vector::insert and vector::reserve
auto c10 = std::ranges::to<Cont4<V, Insert>>(l);
static_assert(std::is_same_v<decltype(c10), Cont4<V, Insert>>);
VERIFY( c10.c == V(l.begin(), l.end()) );
VERIFY( c10.kind == Insert );
VERIFY( c10.used_reserve );
// use vector::insert and vector::reserve
auto c11 = std::ranges::to<Cont4<V, Insert>>(l2, Alloc(78));
static_assert(std::is_same_v<decltype(c11), Cont4<V, Insert>>);
VERIFY( c11.c == V(l2.begin(), l2.end()) );
VERIFY( c11.c.get_allocator() == Alloc(78) );
VERIFY( c11.kind == Insert );
VERIFY( c11.used_reserve );
// use list::insert
auto c12 = std::ranges::to<Cont4<List, Insert>>(c.c, Alloc2(99));
static_assert(std::is_same_v<decltype(c12), Cont4<List, Insert>>);
VERIFY( c12.c == List(l.begin(), l.end()) );
VERIFY( c12.c.get_allocator() == Alloc(99) );
VERIFY( c12.kind == Insert );
VERIFY( ! c12.used_reserve );
struct NoCopyPls
{
NoCopyPls(int) { }
NoCopyPls(const NoCopyPls&) { throw; }
};
// Uses list::emplace_back(const int&) not list::push_back(NoCopyPls&&).
(void) std::ranges::to<std::list<NoCopyPls>>(l);
}
void