ranges::to
constructs associative containers via c.emplace(c.end(), *it)
Section: 25.5.7.1 [range.utility.conv.general] Status: New Submitter: Hewill Kang Opened: 2024-07-16 Last modified: 2024-08-02
Priority: 2
View all issues with New status.
Discussion:
When ranges::to
constructs an associative container, if there is no range version constructor
for C
and the input range is not common range, ranges::to
will dispatch to the
bullet 25.5.7.2 [range.utility.conv.to] (2.1.4), which first default-constructs the container
and emplaces the element through c.emplace(c.end(), *it)
.
emplace()
on an associative container as it does
not expect an iterator as the first argument, and since map::emplace()
, for instance, is not
constrained, which turns out a hard error because we are trying to make a pair
with
{it, pair}
.
Given that libstdc++ currently does not implement the range constructor for associative containers, the following
illustrates the issue:
#include <ranges>
#include <set>
auto s = std::views::iota(0)
| std::views::take(5)
| std::ranges::to<std::set>(); // hard error
The proposed resolution simply removes the emplace()
branch. Although this means that we always use
insert()
to fill associative containers, such an impact seems negligible.
[2024-07-23; This was caused by LWG 4016.]
[2024-08-02; Reflector poll]
Set priority to 2 after reflector poll.
"Would like to preserve the ability to use emplace
. Tim suggested trying
emplace_hint
first, then emplace
."
"I tried it, it gets very verbose, because we might also want to try
insert(*it)
instead of insert(c.end(), *it)
if emplace(*it)
is not valid
for associative containers, because c.end()
might not be a good hint."
"It might be suboptimal, but it still works."
Proposed resolution:
This wording is relative to N4986.
Modify 25.5.7.1 [range.utility.conv.general] as indicated:
-4- Let
container-appendable
be defined as follows:template<class Container, class Ref> constexpr bool container-appendable = // exposition only requires(Container& c, Ref&& ref) { requires (requires { c.emplace_back(std::forward<Ref>(ref)); } || requires { c.push_back(std::forward<Ref>(ref)); } ||requires { c.emplace(c.end(), std::forward<Ref>(ref)); } ||requires { c.insert(c.end(), std::forward<Ref>(ref)); }); };-5- Let
container-append
be defined as follows:template<class Container> constexpr auto container-append(Container& c) { // exposition only return [&c]<class Ref>(Ref&& ref) { if constexpr (requires { c.emplace_back(declval<Ref>()); }) c.emplace_back(std::forward<Ref>(ref)); else if constexpr (requires { c.push_back(declval<Ref>()); }) c.push_back(std::forward<Ref>(ref));else if constexpr (requires { c.emplace(c.end(), declval<Ref>()); }) c.emplace(c.end(), std::forward<Ref>(ref));else c.insert(c.end(), std::forward<Ref>(ref)); }; };