[libcxx-commits] [libcxx] [libc++] Uniformly require complete types in vector member functions (PR #211110)

via libcxx-commits libcxx-commits at lists.llvm.org
Tue Jul 21 13:56:08 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libcxx

Author: Louis Dionne (ldionne)

<details>
<summary>Changes</summary>

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

---
Full diff: https://github.com/llvm/llvm-project/pull/211110.diff


7 Files Affected:

- (modified) libcxx/docs/ReleaseNotes/24.rst (+4) 
- (modified) libcxx/include/CMakeLists.txt (+1) 
- (added) libcxx/include/__utility/require_complete.h (+42) 
- (modified) libcxx/include/__vector/vector.h (+17) 
- (modified) libcxx/include/module.modulemap.in (+1) 
- (removed) libcxx/test/libcxx/containers/sequences/vector/incomplete_type.compile.pass.cpp (-27) 
- (added) libcxx/test/libcxx/containers/sequences/vector/incomplete_type.verify.cpp (+30) 


``````````diff
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
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/211110


More information about the libcxx-commits mailing list