4121. 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).

However, this is not the correct way to call 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.

  1. 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));
      };
    };