4016. container-insertable checks do not match what container-inserter does

Section: 26.5.7 [range.utility.conv] Status: New Submitter: Jonathan Wakely Opened: 2023-11-24 Last modified: 2023-11-26 17:51:54 UTC

Priority: Not Prioritized

View all issues with New status.


The exposition-only helper container-inserter uses either std::back_inserter or std::inserter. Both std::back_insert_iterator and std::insert_iterator require C::value_type to be a valid type, and we do not check for that in container-insertable. The insert iterators can also incur a conversion to construct a C::value_type which then gets moved into the container. Using emplace instead of insert would avoid that temporary object. It's also possible (although arguably not worth caring about) that range_value_t<C> is not the same type as C::value_type, and that conversion to C::value_type could be ill-formed (we only check that conversion from range_reference_t<R> to range_value_t<C> is well-formed).

It seems preferable to remove the use of insert iterators, so that we don't need to check their requirements at all.

[2023-1-26; Rename exposition-only concept and function after reflector discussion.]

Proposed resolution:

This wording is relative to N4964.

  1. Modify [range.utility.conv.general] as indicated:

    -4- Let container-insertableappendable be defined as follows:

    template<class Container, class Ref>
    constexpr bool container-insertableappendable =         // 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-inserterappend be defined as follows:

    template<class Container, class Ref>
    constexpr auto container-inserterappend(Container& c) {     // exposition only
      if constexpr (requires { c.push_back(declval<Ref>()); })
        return back_inserter(c);
        return inserter(c, c.end());
      return [&c]<class Ref>(Ref&& ref) {
        if constexpr (requires { c.emplace_back(declval<Ref>()); })
        else if constexpr (requires { c.push_back(declval<Ref>()); })
        else if constexpr (requires { c.emplace(c.end(), declval<Ref>()); })
          c.emplace(c.end(), std::forward<Ref>(ref));
          c.insert(c.end(), std::forward<Ref>(ref));

  2. Modify [range.utility.conv.to] as indicated:

    (2.1.4) Otherwise, if

    • constructible_from<C, Args...> is true, and
    • container-insertableappendable<C, range_reference_t<R>> is true:
    C c(std::forward<Args>(args)...);
    if constexpr (sized_range<R> && reservable-container<C>)
    ranges::copyfor_each(r, container-inserterappend<range_reference_t<R>>(c));