[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
Thu Sep 10 23:14:40 PDT 2026
https://github.com/Nawazz-ish updated https://github.com/llvm/llvm-project/pull/220481
>From 65ab28c9bc0128581a5556849bc39ab8bda13812 Mon Sep 17 00:00:00 2001
From: Nawaz <enzishh at icloud.com>
Date: Wed, 19 Aug 2026 08:32:59 +0530
Subject: [PATCH 1/2] [libc++][ranges] LWG4121: ranges::to constructs
associative containers via c.emplace(c.end(), *it)
Associative containers forward the arguments of emplace() to the element
constructor rather than treating the first one as a position hint, so
c.emplace(c.end(), *it) does not do what the append path intended. Use
emplace_hint(c.end(), *it) instead, in both the container-appendable concept
and the append branch.
The test container is renamed to match. After this change there is no
emplace(pos, val) branch left, so InserterChoice::Emplace becomes EmplaceHint
rather than gaining a new value alongside it.
Renaming that container shows the new member is called, but not that the old
one is no longer selected. test_lwg_4121 covers the second half: a container
offering only the unhinted emplace(pos, val) alongside insert must now be
appended to via insert. It is kept out of the InserterChoice ladder, which is
a cumulative rank, so that adding it does not hand every EmplaceHint container
an emplace() and weaken the existing priority test.
---
libcxx/docs/Status/Cxx29Issues.csv | 2 +-
libcxx/include/__ranges/to.h | 6 +-
.../range.utility.conv/container.h | 8 +--
.../range.utility.conv/to.pass.cpp | 56 ++++++++++++++++++-
4 files changed, 62 insertions(+), 10 deletions(-)
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;
>From 8d85256227ea9404aa5a275fd8f68946ad7b7dad Mon Sep 17 00:00:00 2001
From: Nawaz <enzishh at icloud.com>
Date: Fri, 11 Sep 2026 11:44:29 +0530
Subject: [PATCH 2/2] [libc++] Address review feedback on the LWG4121 test
EmplaceOrInsert::size() returned int, and ranges::equal compares it against
std::array::size(), which is a -Wsign-compare error on the GCC lane. Return
std::size_t, matching the Container in container.h.
Also rename __insert_impl to insert_impl and make it private, since names
starting with a double underscore are reserved for the implementation.
---
.../ranges/range.utility/range.utility.conv/to.pass.cpp | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
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 8d9e0713450a7..1ba4a282ac864 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
@@ -511,19 +511,20 @@ struct EmplaceOrInsert {
constexpr int* begin() { return buffer_; }
constexpr int* end() { return buffer_ + size_; }
- constexpr int size() const { return size_; }
+ constexpr std::size_t size() const { return size_; }
constexpr int* emplace(int* where, int val) {
called_emplace = true;
- return __insert_impl(where, val);
+ return insert_impl(where, val);
}
constexpr int* insert(int* where, int val) {
called_insert = true;
- return __insert_impl(where, val);
+ return insert_impl(where, val);
}
- constexpr int* __insert_impl(int* where, int val) {
+private:
+ constexpr int* insert_impl(int* where, int val) {
assert(size() + 1 <= Capacity);
std::shift_right(where, end(), 1);
*where = val;
More information about the libcxx-commits
mailing list