[libcxx-commits] [libcxx] [libc++][ranges] LWG4121: `ranges::to` constructs associative containers via `c.emplace(c.end(), *it)` (PR #220481)
via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Sep 1 22:02:54 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libcxx
Author: Nawazz-ish
<details>
<summary>Changes</summary>
Implements the resolution of LWG4121.
`ranges::to`'s append path probed for `c.emplace(c.end(), *it)`. Associative containers forward every argument of `emplace()` to the element constructor instead of treating the first one as a position hint, so that probe does not mean what the wording intended. The resolution spells the hinted form `emplace_hint`.
Both the exposition-only *container-appendable* concept and the append branch are updated. The test container is renamed to match, since after this change there is no `emplace(pos, val)` branch left to cover. `EmplaceOrInsert` exercises the behavioural change directly: a container offering only the unhinted `emplace` now has to be appended to via `insert`.
Fixes #<!-- -->204441
---
Full diff: https://github.com/llvm/llvm-project/pull/220481.diff
4 Files Affected:
- (modified) libcxx/docs/Status/Cxx29Issues.csv (+1-1)
- (modified) libcxx/include/__ranges/to.h (+3-3)
- (modified) libcxx/test/std/ranges/range.utility/range.utility.conv/container.h (+4-4)
- (modified) libcxx/test/std/ranges/range.utility/range.utility.conv/to.pass.cpp (+54-2)
``````````diff
diff --git a/libcxx/docs/Status/Cxx29Issues.csv b/libcxx/docs/Status/Cxx29Issues.csv
index d84eeb44a23ff..410b318344d4c 100644
--- a/libcxx/docs/Status/Cxx29Issues.csv
+++ b/libcxx/docs/Status/Cxx29Issues.csv
@@ -1,6 +1,6 @@
"Issue #","Issue Name","Meeting","Status","First released version","GitHub issue","Notes"
"`LWG3417 <https://wg21.link/LWG3417>`__","Missing ``volatile`` atomic deprecations","2026-06 (Brno)","","","`#204440 <https://github.com/llvm/llvm-project/issues/204440>`__",""
-"`LWG4121 <https://wg21.link/LWG4121>`__","``ranges::to`` constructs associative containers via ``c.emplace(c.end(), *it)``","2026-06 (Brno)","","","`#204441 <https://github.com/llvm/llvm-project/issues/204441>`__",""
+"`LWG4121 <https://wg21.link/LWG4121>`__","``ranges::to`` constructs associative containers via ``c.emplace(c.end(), *it)``","2026-06 (Brno)","|Complete|","24","`#204441 <https://github.com/llvm/llvm-project/issues/204441>`__",""
"`LWG4125 <https://wg21.link/LWG4125>`__","``move_iterator``'s default constructor should be constrained","2026-06 (Brno)","|Complete|","23","`#204442 <https://github.com/llvm/llvm-project/issues/204442>`__",""
"`LWG4158 <https://wg21.link/LWG4158>`__","``packaged_task::operator=`` should abandon its shared state","2026-06 (Brno)","","","`#204443 <https://github.com/llvm/llvm-project/issues/204443>`__",""
"`LWG4182 <https://wg21.link/LWG4182>`__","Definition of ``NULL`` is too broad","2026-06 (Brno)","","","`#204444 <https://github.com/llvm/llvm-project/issues/204444>`__",""
diff --git a/libcxx/include/__ranges/to.h b/libcxx/include/__ranges/to.h
index 3a2bc42969f7b..3734916530eae 100644
--- a/libcxx/include/__ranges/to.h
+++ b/libcxx/include/__ranges/to.h
@@ -57,7 +57,7 @@ constexpr bool __container_appendable = 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.emplace_hint(__c.end(), std::forward<_Ref>(__ref)); } ||
requires { __c.insert(__c.end(), std::forward<_Ref>(__ref)); });
};
@@ -117,8 +117,8 @@ template <class _Container, input_range _Range, class... _Args>
__result.emplace_back(std::forward<_Ref>(__ref));
} else if constexpr (requires { __result.push_back(std::declval<_Ref>()); }) {
__result.push_back(std::forward<_Ref>(__ref));
- } else if constexpr (requires { __result.emplace(__result.end(), std::declval<_Ref>()); }) {
- __result.emplace(__result.end(), std::forward<_Ref>(__ref));
+ } else if constexpr (requires { __result.emplace_hint(__result.end(), std::declval<_Ref>()); }) {
+ __result.emplace_hint(__result.end(), std::forward<_Ref>(__ref));
} else {
static_assert(requires { __result.insert(__result.end(), std::declval<_Ref>()); });
__result.insert(__result.end(), std::forward<_Ref>(__ref));
diff --git a/libcxx/test/std/ranges/range.utility/range.utility.conv/container.h b/libcxx/test/std/ranges/range.utility/range.utility.conv/container.h
index 4fd52680add9b..b7927fcf2ab8d 100644
--- a/libcxx/test/std/ranges/range.utility/range.utility.conv/container.h
+++ b/libcxx/test/std/ranges/range.utility/range.utility.conv/container.h
@@ -14,7 +14,7 @@
enum class CtrChoice { Invalid, DefaultCtrAndInsert, BeginEndPair, FromRangeT, DirectCtr };
-enum class InserterChoice { Invalid, Insert, Emplace, PushBack, EmplaceBack };
+enum class InserterChoice { Invalid, Insert, EmplaceHint, PushBack, EmplaceBack };
// Allows checking that `ranges::to` correctly follows the order of priority of different constructors -- e.g., if
// 3 constructors are available, the `from_range_t` constructor is chosen in favor of the constructor taking two
@@ -118,10 +118,10 @@ struct Container {
}
template <class T>
- constexpr ElementType* emplace(ElementType* where, T val)
- requires(Inserter >= InserterChoice::Emplace)
+ constexpr ElementType* emplace_hint(ElementType* where, T val)
+ requires(Inserter >= InserterChoice::EmplaceHint)
{
- inserter_choice = InserterChoice::Emplace;
+ inserter_choice = InserterChoice::EmplaceHint;
return __insert_impl(where, val);
}
diff --git a/libcxx/test/std/ranges/range.utility/range.utility.conv/to.pass.cpp b/libcxx/test/std/ranges/range.utility/range.utility.conv/to.pass.cpp
index a983745fd636e..8d9e0713450a7 100644
--- a/libcxx/test/std/ranges/range.utility/range.utility.conv/to.pass.cpp
+++ b/libcxx/test/std/ranges/range.utility/range.utility.conv/to.pass.cpp
@@ -435,8 +435,8 @@ constexpr void test_ctr_choice_order() {
case_4.operator()<InserterChoice::Insert, false>();
case_4.operator()<InserterChoice::Insert, true>();
- case_4.operator()<InserterChoice::Emplace, false>();
- case_4.operator()<InserterChoice::Emplace, true>();
+ case_4.operator()<InserterChoice::EmplaceHint, false>();
+ case_4.operator()<InserterChoice::EmplaceHint, true>();
case_4.operator()<InserterChoice::PushBack, false>();
case_4.operator()<InserterChoice::PushBack, true>();
case_4.operator()<InserterChoice::EmplaceBack, false>();
@@ -496,6 +496,57 @@ constexpr void test_lwg_3785() {
}
}
+// A container that spells the hinted insert as the unhinted `emplace`. Before LWG 4121,
+// `ranges::to` probed for `c.emplace(c.end(), *it)` and would call this, passing the end iterator
+// as the first constructor argument. Associative containers spell the hinted form `emplace_hint`,
+// so this overload must no longer be selected and appending has to fall through to `insert`.
+struct EmplaceOrInsert {
+ using value_type = int;
+
+ static constexpr int Capacity = 8;
+ int buffer_[Capacity] = {};
+ int size_ = 0;
+ bool called_emplace = false;
+ bool called_insert = false;
+
+ constexpr int* begin() { return buffer_; }
+ constexpr int* end() { return buffer_ + size_; }
+ constexpr int size() const { return size_; }
+
+ constexpr int* emplace(int* where, int val) {
+ called_emplace = true;
+ return __insert_impl(where, val);
+ }
+
+ constexpr int* insert(int* where, int val) {
+ called_insert = true;
+ return __insert_impl(where, val);
+ }
+
+ constexpr int* __insert_impl(int* where, int val) {
+ assert(size() + 1 <= Capacity);
+ std::shift_right(where, end(), 1);
+ *where = val;
+ ++size_;
+ return where;
+ }
+};
+
+constexpr void test_lwg_4121() {
+ // Test LWG 4121 ("`ranges::to` constructs associative containers via `c.emplace(c.end(), *it)`").
+ // `emplace` on an associative container forwards every argument to the element constructor rather
+ // than treating the first as a position hint, so the hinted form must be spelled `emplace_hint`.
+ // A container offering only the unhinted `emplace` has to be appended to via `insert`.
+ std::array in = {1, 2, 3};
+
+ std::same_as<EmplaceOrInsert> decltype(auto) result = std::ranges::to<EmplaceOrInsert>(in);
+
+ assert(!result.called_emplace);
+ assert(result.called_insert);
+ assert(result.size() == 3);
+ assert(std::ranges::equal(result, in));
+}
+
constexpr void test_recursive() {
using C1 = Container<int, CtrChoice::DirectCtr>;
using C2 = Container<C1, CtrChoice::FromRangeT>;
@@ -554,6 +605,7 @@ constexpr bool test() {
test_constraints();
test_ctr_choice_order();
test_lwg_3785();
+ test_lwg_4121();
test_recursive();
return true;
``````````
</details>
https://github.com/llvm/llvm-project/pull/220481
More information about the libcxx-commits
mailing list