[libcxx-commits] [libcxx] [libc++] Uniformly require complete types in vector member functions (PR #211110)
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Jul 21 13:55:30 PDT 2026
https://github.com/ldionne created https://github.com/llvm/llvm-project/pull/211110
The Standard allows vector's value type to be incomplete when instantiating the vector class, but it requires it to be complete before instantiating any member function. Libc++ would produce an error for most functions, but it would silently work for a few methods like empty() (however only in the non size-based vector).
To avoid portability traps for users, uniformly require complete types under all configurations for all vector methods.
See also #210754
>From c0d766a1104c92315f275a7febdbef26ff65b636 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 21 Jul 2026 16:51:27 -0400
Subject: [PATCH] [libc++] Uniformly require complete types in vector member
functions
The Standard allows vector's value type to be incomplete when instantiating
the vector class, but it requires it to be complete before instantiating
any member function. Libc++ would produce an error for most functions,
but it would silently work for a few methods like empty() (however only
in the non size-based vector).
To avoid portability traps for users, uniformly require complete types
under all configurations for all vector methods.
See also #210754
---
libcxx/docs/ReleaseNotes/24.rst | 4 ++
libcxx/include/CMakeLists.txt | 1 +
libcxx/include/__utility/require_complete.h | 42 +++++++++++++++++++
libcxx/include/__vector/vector.h | 17 ++++++++
libcxx/include/module.modulemap.in | 1 +
.../vector/incomplete_type.compile.pass.cpp | 27 ------------
.../vector/incomplete_type.verify.cpp | 30 +++++++++++++
7 files changed, 95 insertions(+), 27 deletions(-)
create mode 100644 libcxx/include/__utility/require_complete.h
delete mode 100644 libcxx/test/libcxx/containers/sequences/vector/incomplete_type.compile.pass.cpp
create mode 100644 libcxx/test/libcxx/containers/sequences/vector/incomplete_type.verify.cpp
diff --git a/libcxx/docs/ReleaseNotes/24.rst b/libcxx/docs/ReleaseNotes/24.rst
index 70c704ff9d326..612fb0d03d0b2 100644
--- a/libcxx/docs/ReleaseNotes/24.rst
+++ b/libcxx/docs/ReleaseNotes/24.rst
@@ -57,6 +57,10 @@ Potentially breaking changes
but causes programs which rely on these includes to not compile anymore. The ``_LIBCPP_KEEP_TRANSITIVE_INCLUDES_LLVM23``
macro that was provided in LLVM 23 to ease the transition has been removed in this release.
+- Libc++ now diagnoses when a method of ``std::vector<T>`` is used and ``T`` is an incomplete type. The Standard requires
+ ``T`` to be complete before any member function is used, however this worked unreliably for a few member functions. In
+ LLVM 24, this is diagnosed uniformly.
+
Announcements About Future Releases
-----------------------------------
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index b40f586161e62..3f7be893a4450 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -953,6 +953,7 @@ set(files
__utility/priority_tag.h
__utility/private_constructor_tag.h
__utility/rel_ops.h
+ __utility/require_complete.h
__utility/scope_guard.h
__utility/small_buffer.h
__utility/swap.h
diff --git a/libcxx/include/__utility/require_complete.h b/libcxx/include/__utility/require_complete.h
new file mode 100644
index 0000000000000..5cb9ea74b500b
--- /dev/null
+++ b/libcxx/include/__utility/require_complete.h
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___UTILITY_REQUIRE_COMPLETE_H
+#define _LIBCPP___UTILITY_REQUIRE_COMPLETE_H
+
+#include <__config>
+#include <__cstddef/size_t.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+template <class _Tp, size_t = sizeof(_Tp)>
+_LIBCPP_CONSTEXPR void __require_complete_impl(int) {}
+
+template <class _Tp, bool _False = false>
+_LIBCPP_CONSTEXPR void __require_complete_impl(long) {
+ static_assert(_False, "Type is required to be complete");
+}
+
+// Produce a compiler error if the given type is not complete.
+template <class _Tp>
+_LIBCPP_CONSTEXPR void __require_complete() {
+ std::__require_complete_impl<_Tp>(0);
+}
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___UTILITY_REQUIRE_COMPLETE_H
diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h
index 5e9fa4a7d0030..6749ae1aacc03 100644
--- a/libcxx/include/__vector/vector.h
+++ b/libcxx/include/__vector/vector.h
@@ -65,6 +65,7 @@
#include <__utility/is_pointer_in_range.h>
#include <__utility/move.h>
#include <__utility/pair.h>
+#include <__utility/require_complete.h>
#include <__utility/swap.h>
#include <initializer_list>
#include <limits>
@@ -348,43 +349,55 @@ class vector {
// Iterators
//
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator begin() _NOEXCEPT {
+ std::__require_complete<_Tp>();
return __make_iter(__add_alignment_assumption(this->__layout_.__begin_ptr()));
}
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_iterator begin() const _NOEXCEPT {
+ std::__require_complete<_Tp>();
return __make_iter(__add_alignment_assumption(this->__layout_.__begin_ptr()));
}
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator end() _NOEXCEPT {
+ std::__require_complete<_Tp>();
return __make_iter(__add_alignment_assumption(__layout_.__end_ptr()));
}
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_iterator end() const _NOEXCEPT {
+ std::__require_complete<_Tp>();
return __make_iter(__add_alignment_assumption(__layout_.__end_ptr()));
}
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI reverse_iterator rbegin() _NOEXCEPT {
+ std::__require_complete<_Tp>();
return reverse_iterator(end());
}
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_reverse_iterator
rbegin() const _NOEXCEPT {
+ std::__require_complete<_Tp>();
return const_reverse_iterator(end());
}
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI reverse_iterator rend() _NOEXCEPT {
+ std::__require_complete<_Tp>();
return reverse_iterator(begin());
}
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_reverse_iterator rend() const _NOEXCEPT {
+ std::__require_complete<_Tp>();
return const_reverse_iterator(begin());
}
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_iterator cbegin() const _NOEXCEPT {
+ std::__require_complete<_Tp>();
return begin();
}
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_iterator cend() const _NOEXCEPT {
+ std::__require_complete<_Tp>();
return end();
}
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_reverse_iterator
crbegin() const _NOEXCEPT {
+ std::__require_complete<_Tp>();
return rbegin();
}
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI const_reverse_iterator crend() const _NOEXCEPT {
+ std::__require_complete<_Tp>();
return rend();
}
@@ -392,16 +405,20 @@ class vector {
// [vector.capacity], capacity
//
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type size() const _NOEXCEPT {
+ std::__require_complete<_Tp>();
return __layout_.__size();
}
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type capacity() const _NOEXCEPT {
+ std::__require_complete<_Tp>();
return __layout_.__capacity();
}
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool empty() const _NOEXCEPT {
+ std::__require_complete<_Tp>();
return __layout_.__empty();
}
[[__nodiscard__]] _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI size_type max_size() const _NOEXCEPT {
+ std::__require_complete<_Tp>();
return std::min<size_type>(__alloc_traits::max_size(__layout_.__alloc()), numeric_limits<difference_type>::max());
}
_LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void reserve(size_type __n);
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 39b4e0bb986c6..3a2615da37505 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -2229,6 +2229,7 @@ module std {
module priority_tag { header "__utility/priority_tag.h" }
module private_constructor_tag { header "__utility/private_constructor_tag.h" }
module rel_ops { header "__utility/rel_ops.h" }
+ module require_complete { header "__utility/require_complete.h" }
module scope_guard { header "__utility/scope_guard.h" }
module small_buffer { header "__utility/small_buffer.h" }
module swap { header "__utility/swap.h" }
diff --git a/libcxx/test/libcxx/containers/sequences/vector/incomplete_type.compile.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/incomplete_type.compile.pass.cpp
deleted file mode 100644
index 029d29eb437f8..0000000000000
--- a/libcxx/test/libcxx/containers/sequences/vector/incomplete_type.compile.pass.cpp
+++ /dev/null
@@ -1,27 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-// <vector>
-
-// This test pins down the current libc++ behavior that vector<T>::empty() can be
-// called even when T is an incomplete type. The standard does not require this:
-// [vector.overview] only guarantees that an incomplete type may be used to
-// instantiate vector, and requires the type to be complete before any method is
-// called.
-//
-// However, libc++ made that work previously, and this test pins down that behavior
-// to avoid breaking it unintentionally. Note that this is not a guarantee to users
-// that we will support this in the future: this merely guards against changing this
-// behavior unknowingly.
-
-#include <vector>
-
-struct Incomplete;
-
-bool call_empty(std::vector<Incomplete>& v) { return v.empty(); }
-bool call_empty_const(const std::vector<Incomplete>& v) { return v.empty(); }
diff --git a/libcxx/test/libcxx/containers/sequences/vector/incomplete_type.verify.cpp b/libcxx/test/libcxx/containers/sequences/vector/incomplete_type.verify.cpp
new file mode 100644
index 0000000000000..caabd64c3b279
--- /dev/null
+++ b/libcxx/test/libcxx/containers/sequences/vector/incomplete_type.verify.cpp
@@ -0,0 +1,30 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <vector>
+
+// This test ensures that we diagnose when an incomplete type is used in one of
+// vector's methods. The Standard requires that to be the case, and we want to
+// uniformly produce an error for that. Note that producing the same diagnostic
+// in all cases is difficult, but we at least want to fail to fight back against
+// Hyrum's law.
+
+#include <vector>
+
+struct Incomplete;
+
+void f(std::vector<Incomplete>& v) {
+ (void)v.empty(); // expected-error@*:* {{}}
+ (void)v.size(); // expected-error@*:* {{}}
+ (void)v.begin(); // expected-error@*:* {{}}
+ (void)v.end(); // expected-error@*:* {{}}
+ (void)v.cbegin(); // expected-error@*:* {{}}
+ (void)v.cend(); // expected-error@*:* {{}}
+
+ // etc for other APIs
+}
More information about the libcxx-commits
mailing list