[libcxx-commits] [libcxx] [libc++] Implement P2538R1 "ADL-proof std::projected" (PR #65411)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Wed Sep 6 08:30:15 PDT 2023


https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/65411:

>From 23567ab14509413d47d6a165cccbb36c02cc0841 Mon Sep 17 00:00:00 2001
From: Arthur O'Dwyer <arthur.j.odwyer at gmail.com>
Date: Tue, 5 Sep 2023 16:12:02 -0400
Subject: [PATCH] [libc++] Implement P2538R1 "ADL-proof std::projected"

Notice that because Holder<Incomplete> is _possible_ to complete,
but _unsafe_ to complete, that means that Holder<Incomplete>*
is basically not an iterator and it's not even safe to ask if
input_iterator<Holder<Incomplete>*> because that _will_ necessarily
complete the type. So it's totally expected that we still cannot
safely ask e.g.

  static_assert(std::indirect_unary_predicate<bool(&)(Holder<Incomplete>&), Holder<Incomplete>*>);

or even

  static_assert(!std::indirect_unary_predicate<int, Holder<Incomplete>*>);

Differential Revision: https://reviews.llvm.org/D119029

Co-authored-by: Louis Dionne <ldionne.2 at gmail.com>
---
 libcxx/docs/ReleaseNotes/18.rst               |  6 ++++
 libcxx/docs/Status/Cxx2cPapers.csv            |  2 +-
 libcxx/include/__iterator/projected.h         | 28 +++++++++++++------
 ...indirect_binary_predicate.compile.pass.cpp | 10 +++++++
 ...rect_equivalence_relation.compile.pass.cpp | 12 +++++++-
 .../indirect_result_t.compile.pass.cpp        | 14 ++++++++++
 ...ndirect_strict_weak_order.compile.pass.cpp | 12 +++++++-
 .../indirect_unary_predicate.compile.pass.cpp | 10 +++++++
 .../indirectly_comparable.compile.pass.cpp    | 10 +++++++
 ...y_regular_unary_invocable.compile.pass.cpp | 10 +++++++
 ...ndirectly_unary_invocable.compile.pass.cpp | 10 +++++++
 libcxx/utils/data/ignore_format.txt           |  1 -
 12 files changed, 113 insertions(+), 12 deletions(-)

diff --git a/libcxx/docs/ReleaseNotes/18.rst b/libcxx/docs/ReleaseNotes/18.rst
index b10c8fa78c2270..36e6d69cd97cc8 100644
--- a/libcxx/docs/ReleaseNotes/18.rst
+++ b/libcxx/docs/ReleaseNotes/18.rst
@@ -42,6 +42,8 @@ Implemented Papers
 - P2497R0 - Testing for success or failure of ``<charconv>`` functions
 - P2697R1 - Interfacing ``bitset`` with ``string_view``
 - P2443R1 - ``views::chunk_by``
+- P2538R1 - ADL-proof ``std::projected``
+
 
 Improvements and New Features
 -----------------------------
@@ -88,5 +90,9 @@ ABI Affecting Changes
   and you notice changes to your exported symbols list, then this means that you were not properly preventing libc++
   symbols from being part of your ABI.
 
+- The name mangling for intantiations of ``std::projected`` has changed in order to implement P2538R1. This technically
+  results in an ABI break, however in practice we expect uses of ``std::projected`` in ABI-sensitive places to be
+  extremely rare. Any error resulting from this change should result in a link-time error.
+
 Build System Changes
 --------------------
diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv
index fe9d17004e2aab..0590e2d487c27b 100644
--- a/libcxx/docs/Status/Cxx2cPapers.csv
+++ b/libcxx/docs/Status/Cxx2cPapers.csv
@@ -5,7 +5,7 @@
 "`P2562R1 <https://wg21.link/P2562R1>`__","LWG","``constexpr`` Stable Sorting","Varna June 2023","","",""
 "`P2545R4 <https://wg21.link/P2545R4>`__","LWG","Read-Copy Update (RCU)","Varna June 2023","","",""
 "`P2530R3 <https://wg21.link/P2530R3>`__","LWG","Hazard Pointers for C++26","Varna June 2023","","",""
-"`P2538R1 <https://wg21.link/P2538R1>`__","LWG","ADL-proof ``std::projected``","Varna June 2023","","","|ranges|"
+"`P2538R1 <https://wg21.link/P2538R1>`__","LWG","ADL-proof ``std::projected``","Varna June 2023","|Complete|","18.0","|ranges|"
 "`P2495R3 <https://wg21.link/P2495R3>`__","LWG","Interfacing ``stringstreams`` with ``string_view``","Varna June 2023","","",""
 "`P2510R3 <https://wg21.link/P2510R3>`__","LWG","Formatting pointers","Varna June 2023","|Complete| [#note-P2510R3]_","17.0","|format|"
 "`P2198R7 <https://wg21.link/P2198R7>`__","LWG","Freestanding Feature-Test Macros and Implementation-Defined Extensions","Varna June 2023","","",""
diff --git a/libcxx/include/__iterator/projected.h b/libcxx/include/__iterator/projected.h
index e74e56d6fb71e7..463d07b0d33c2d 100644
--- a/libcxx/include/__iterator/projected.h
+++ b/libcxx/include/__iterator/projected.h
@@ -12,7 +12,7 @@
 
 #include <__config>
 #include <__iterator/concepts.h>
-#include <__iterator/incrementable_traits.h>
+#include <__iterator/incrementable_traits.h> // iter_difference_t
 #include <__type_traits/remove_cvref.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -23,17 +23,29 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 
 #if _LIBCPP_STD_VER >= 20
 
-template<indirectly_readable _It, indirectly_regular_unary_invocable<_It> _Proj>
-struct projected {
-  using value_type = remove_cvref_t<indirect_result_t<_Proj&, _It>>;
-  indirect_result_t<_Proj&, _It> operator*() const; // not defined
+template <class _It, class _Proj>
+struct __projected_impl {
+  struct __type {
+    using value_type = remove_cvref_t<indirect_result_t<_Proj&, _It>>;
+    indirect_result_t<_Proj&, _It> operator*() const; // not defined
+  };
 };
 
-template<weakly_incrementable _It, class _Proj>
-struct incrementable_traits<projected<_It, _Proj>> {
-  using difference_type = iter_difference_t<_It>;
+template <weakly_incrementable _It, class _Proj>
+struct __projected_impl<_It, _Proj> {
+  struct __type {
+    using value_type      = remove_cvref_t<indirect_result_t<_Proj&, _It>>;
+    using difference_type = iter_difference_t<_It>;
+    indirect_result_t<_Proj&, _It> operator*() const; // not defined
+  };
 };
 
+// Note that we implement std::projected in a way that satisfies P2538R1 even in standard
+// modes before C++26 to avoid breaking the ABI between standard modes (even though ABI
+// breaks with std::projected are expected to have essentially no impact).
+template <indirectly_readable _It, indirectly_regular_unary_invocable<_It> _Proj>
+using projected = typename __projected_impl<_It, _Proj>::__type;
+
 #endif // _LIBCPP_STD_VER >= 20
 
 _LIBCPP_END_NAMESPACE_STD
diff --git a/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_binary_predicate.compile.pass.cpp b/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_binary_predicate.compile.pass.cpp
index 741aeeedaa8c31..a5c4f24764dd78 100644
--- a/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_binary_predicate.compile.pass.cpp
+++ b/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_binary_predicate.compile.pass.cpp
@@ -11,10 +11,12 @@
 // template<class F, class I1, class I2>
 // concept indirect_binary_predicate;
 
+#include <functional>
 #include <iterator>
 #include <type_traits>
 
 #include "indirectly_readable.h"
+#include "test_macros.h"
 
 using It1 = IndirectlyReadable<struct Token1>;
 using It2 = IndirectlyReadable<struct Token2>;
@@ -80,3 +82,11 @@ struct BadPredicate6 {
     bool operator()(std::iter_common_reference_t<It1>, std::iter_common_reference_t<It2>) const = delete;
 };
 static_assert(!std::indirect_binary_predicate<BadPredicate6, It1, It2>);
+
+// Test ADL-proofing (P2538R1)
+#if TEST_STD_VER >= 26
+struct Incomplete;
+template<class T> struct Holder { T t; };
+static_assert(std::indirect_binary_predicate<std::less<Holder<Incomplete>*>, Holder<Incomplete>**, Holder<Incomplete>**>);
+static_assert(!std::indirect_binary_predicate<Holder<Incomplete>*, Holder<Incomplete>**, Holder<Incomplete>**>);
+#endif
diff --git a/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_equivalence_relation.compile.pass.cpp b/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_equivalence_relation.compile.pass.cpp
index 0af5def93d65b9..c6fc8fab42355f 100644
--- a/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_equivalence_relation.compile.pass.cpp
+++ b/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_equivalence_relation.compile.pass.cpp
@@ -11,10 +11,12 @@
 // template<class F, class I1, class I2 = I1>
 // concept indirect_equivalence_relation;
 
-#include <iterator>
 #include <concepts>
+#include <functional>
+#include <iterator>
 
 #include "indirectly_readable.h"
+#include "test_macros.h"
 
 using It1 = IndirectlyReadable<struct Token1>;
 using It2 = IndirectlyReadable<struct Token2>;
@@ -95,3 +97,11 @@ struct BadRelation6 {
     bool operator()(std::iter_common_reference_t<It1>, std::iter_common_reference_t<It2>) const = delete;
 };
 static_assert(!std::indirect_equivalence_relation<BadRelation6, It1, It2>);
+
+// Test ADL-proofing (P2538R1)
+#if TEST_STD_VER >= 26
+struct Incomplete;
+template<class T> struct Holder { T t; };
+static_assert(std::indirect_equivalence_relation<std::equal_to<Holder<Incomplete>*>, Holder<Incomplete>**, Holder<Incomplete>**>);
+static_assert(!std::indirect_equivalence_relation<Holder<Incomplete>*, Holder<Incomplete>**, Holder<Incomplete>**>);
+#endif
diff --git a/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_result_t.compile.pass.cpp b/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_result_t.compile.pass.cpp
index 4121170c3e46cc..d6f76269da3773 100644
--- a/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_result_t.compile.pass.cpp
+++ b/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_result_t.compile.pass.cpp
@@ -13,6 +13,8 @@
 #include <iterator>
 #include <concepts>
 
+#include "test_macros.h"
+
 static_assert(std::same_as<std::indirect_result_t<int (*)(int), int*>, int>);
 static_assert(std::same_as<std::indirect_result_t<double (*)(int const&, float), int const*, float*>, double>);
 
@@ -29,3 +31,15 @@ constexpr bool has_indirect_result = requires {
 
 static_assert(!has_indirect_result<int (*)(int), int>); // int isn't indirectly_readable
 static_assert(!has_indirect_result<int, int*>);         // int isn't invocable
+
+// Test ADL-proofing (P2538R1)
+#if TEST_STD_VER >= 26
+struct Incomplete;
+template<class T> struct Holder { T t; };
+static_assert(std::same_as<std::indirect_result_t<int                (&)(int), int*>, int>);
+static_assert(std::same_as<std::indirect_result_t<Holder<Incomplete>&(&)(int), int*>, Holder<Incomplete>&>);
+static_assert(std::same_as<std::indirect_result_t<Holder<Incomplete>*(&)(int), int*>, Holder<Incomplete>*>);
+static_assert(std::same_as<std::indirect_result_t<int                (&)(Holder<Incomplete>*), Holder<Incomplete>**>, int>);
+static_assert(std::same_as<std::indirect_result_t<Holder<Incomplete>&(&)(Holder<Incomplete>*), Holder<Incomplete>**>, Holder<Incomplete>&>);
+static_assert(std::same_as<std::indirect_result_t<Holder<Incomplete>*(&)(Holder<Incomplete>*), Holder<Incomplete>**>, Holder<Incomplete>*>);
+#endif
diff --git a/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_strict_weak_order.compile.pass.cpp b/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_strict_weak_order.compile.pass.cpp
index 5a5360cf208dd3..7828e105ba397f 100644
--- a/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_strict_weak_order.compile.pass.cpp
+++ b/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_strict_weak_order.compile.pass.cpp
@@ -11,10 +11,12 @@
 // template<class F, class I1, class I2 = I1>
 // concept indirect_strict_weak_order;
 
-#include <iterator>
 #include <concepts>
+#include <functional>
+#include <iterator>
 
 #include "indirectly_readable.h"
+#include "test_macros.h"
 
 using It1 = IndirectlyReadable<struct Token1>;
 using It2 = IndirectlyReadable<struct Token2>;
@@ -95,3 +97,11 @@ struct BadOrder6 {
     bool operator()(std::iter_common_reference_t<It1>, std::iter_common_reference_t<It2>) const = delete;
 };
 static_assert(!std::indirect_strict_weak_order<BadOrder6, It1, It2>);
+
+// Test ADL-proofing (P2538R1)
+#if TEST_STD_VER >= 26
+struct Incomplete;
+template<class T> struct Holder { T t; };
+static_assert(std::indirect_strict_weak_order<std::less<Holder<Incomplete>*>, Holder<Incomplete>**, Holder<Incomplete>**>);
+static_assert(!std::indirect_strict_weak_order<Holder<Incomplete>*, Holder<Incomplete>**, Holder<Incomplete>**>);
+#endif
diff --git a/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_unary_predicate.compile.pass.cpp b/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_unary_predicate.compile.pass.cpp
index 05e359d0cdf2b1..7b551d567959df 100644
--- a/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_unary_predicate.compile.pass.cpp
+++ b/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirect_unary_predicate.compile.pass.cpp
@@ -15,6 +15,7 @@
 #include <type_traits>
 
 #include "indirectly_readable.h"
+#include "test_macros.h"
 
 using It = IndirectlyReadable<struct Token>;
 
@@ -62,3 +63,12 @@ struct BadPredicate4 {
     bool operator()(std::iter_common_reference_t<It>) const = delete;
 };
 static_assert(!std::indirect_unary_predicate<BadPredicate4, It>);
+
+// Test ADL-proofing (P2538R1)
+#if TEST_STD_VER >= 26
+struct Incomplete;
+template<class T> struct Holder { T t; };
+struct HolderIncompletePred { bool operator()(Holder<Incomplete>*) const; };
+static_assert(std::indirect_unary_predicate<HolderIncompletePred, Holder<Incomplete>**>);
+static_assert(!std::indirect_unary_predicate<Holder<Incomplete>*, Holder<Incomplete>**>);
+#endif
diff --git a/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirectly_comparable.compile.pass.cpp b/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirectly_comparable.compile.pass.cpp
index eae5b342d5986a..c171d092567e78 100644
--- a/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirectly_comparable.compile.pass.cpp
+++ b/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirectly_comparable.compile.pass.cpp
@@ -15,6 +15,8 @@
 #include <iterator>
 #include <type_traits>
 
+#include "test_macros.h"
+
 struct Deref {
     int operator()(int*) const;
 };
@@ -48,3 +50,11 @@ void is_subsumed(F);
 
 static_assert(subsumes(std::less<int>()));
 static_assert(is_subsumed(std::less<int>()));
+
+// Test ADL-proofing (P2538R1)
+#if TEST_STD_VER >= 26
+struct Incomplete;
+template<class T> struct Holder { T t; };
+static_assert(std::indirectly_comparable<Holder<Incomplete>**, Holder<Incomplete>**, std::less<Holder<Incomplete>*>>);
+static_assert(!std::indirectly_comparable<Holder<Incomplete>**, Holder<Incomplete>**, Holder<Incomplete>*>);
+#endif
diff --git a/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirectly_regular_unary_invocable.compile.pass.cpp b/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirectly_regular_unary_invocable.compile.pass.cpp
index 333b9fadbba599..85d37bbd7bed49 100644
--- a/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirectly_regular_unary_invocable.compile.pass.cpp
+++ b/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirectly_regular_unary_invocable.compile.pass.cpp
@@ -15,6 +15,7 @@
 #include <concepts>
 
 #include "indirectly_readable.h"
+#include "test_macros.h"
 
 using It = IndirectlyReadable<struct Token>;
 using R1 = T1<struct ReturnToken>;
@@ -85,3 +86,12 @@ static_assert(!std::indirectly_regular_unary_invocable<int (*)(int*, int*), int*
 static_assert(!std::indirectly_regular_unary_invocable<int (&)(int*, int*), int*>);
 static_assert(!std::indirectly_regular_unary_invocable<int (S::*)(int*), S*>);
 static_assert(!std::indirectly_regular_unary_invocable<int (S::*)(int*) const, S*>);
+
+// Test ADL-proofing (P2538R1)
+#if TEST_STD_VER >= 26
+struct Incomplete;
+template<class T> struct Holder { T t; };
+struct HolderIncompletePred { bool operator()(Holder<Incomplete>*) const; };
+static_assert(std::indirectly_regular_unary_invocable<HolderIncompletePred, Holder<Incomplete>**>);
+static_assert(!std::indirectly_regular_unary_invocable<Holder<Incomplete>*, Holder<Incomplete>**>);
+#endif
diff --git a/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirectly_unary_invocable.compile.pass.cpp b/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirectly_unary_invocable.compile.pass.cpp
index 8b7c3f1eee0658..8fdb77772bd93c 100644
--- a/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirectly_unary_invocable.compile.pass.cpp
+++ b/libcxx/test/std/iterators/iterator.requirements/indirectcallable/indirectinvocable/indirectly_unary_invocable.compile.pass.cpp
@@ -15,6 +15,7 @@
 #include <concepts>
 
 #include "indirectly_readable.h"
+#include "test_macros.h"
 
 using It = IndirectlyReadable<struct Token>;
 using R1 = T1<struct ReturnToken>;
@@ -85,3 +86,12 @@ static_assert(!std::indirectly_unary_invocable<int (*)(int*, int*), int*>);
 static_assert(!std::indirectly_unary_invocable<int (&)(int*, int*), int*>);
 static_assert(!std::indirectly_unary_invocable<int (S::*)(int*), S*>);
 static_assert(!std::indirectly_unary_invocable<int (S::*)(int*) const, S*>);
+
+// Test ADL-proofing (P2538R1)
+#if TEST_STD_VER >= 26
+struct Incomplete;
+template<class T> struct Holder { T t; };
+struct HolderIncompletePred { bool operator()(Holder<Incomplete>*) const; };
+static_assert(std::indirectly_unary_invocable<HolderIncompletePred, Holder<Incomplete>**>);
+static_assert(!std::indirectly_unary_invocable<Holder<Incomplete>*, Holder<Incomplete>**>);
+#endif
diff --git a/libcxx/utils/data/ignore_format.txt b/libcxx/utils/data/ignore_format.txt
index 3caf9d45c783ec..95eeb8200f97db 100644
--- a/libcxx/utils/data/ignore_format.txt
+++ b/libcxx/utils/data/ignore_format.txt
@@ -284,7 +284,6 @@ libcxx/include/__iterator/ostreambuf_iterator.h
 libcxx/include/__iterator/ostream_iterator.h
 libcxx/include/__iterator/permutable.h
 libcxx/include/__iterator/prev.h
-libcxx/include/__iterator/projected.h
 libcxx/include/__iterator/readable_traits.h
 libcxx/include/__iterator/reverse_access.h
 libcxx/include/__iterator/reverse_iterator.h



More information about the libcxx-commits mailing list