Skip to content

[ADT] Fix llvm::concat_iterator for ValueT == common_base_class * #144744

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

jalopezg-git
Copy link
Contributor

@jalopezg-git jalopezg-git commented Jun 18, 2025

Fix llvm::concat_iterator for the case of ValueT being a pointer to a common base class to which the result of dereferencing any iterator in ItersT can be casted to. In particular, the case below was not working before this patch, but I see no particular reason why it shouldn't be supported.

namespace some_namespace {
struct some_struct {
  std::vector<int> data;
  std::string swap_val;
};

struct derives_from_some_struct : some_struct {
};
} // namespace some_namespace

TEST(STLExtrasTest, ConcatRangePtrToDerivedClass) {
  some_namespace::some_struct S0{};
  some_namespace::derives_from_some_struct S1{};
  SmallVector<some_namespace::some_struct *> V0{&S0};
  SmallVector<some_namespace::derives_from_some_struct *> V1{&S1, &S1};

  // Use concat over ranges of pointers to different (but related) types.
  EXPECT_THAT(concat<some_namespace::some_struct *>(V0, V1),
              ElementsAre(&S0, static_cast<some_namespace::some_struct *>(&S1),
                          static_cast<some_namespace::some_struct *>(&S1)));
}

Per https://en.cppreference.com/w/cpp/language/implicit_conversion.html, Sec. Pointer conversions,

A prvalue ptr of type “pointer to (possibly cv-qualified) Derived” can be converted to a prvalue of type “pointer to (possibly cv-qualified) Base”, where Base is a base class of Derived, and Derived is a complete class type.

I.e. conversion shall be possible, but the former implementation of llvm::concat_iterator forbids that, given that handle_type is pointer-to-pointer type, which is not convertible.

@jalopezg-git jalopezg-git requested review from kuhar and dwblaikie June 18, 2025 16:19
@jalopezg-git jalopezg-git self-assigned this Jun 18, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 18, 2025

@llvm/pr-subscribers-llvm-adt

Author: Javier Lopez-Gomez (jalopezg-git)

Changes

Fix llvm::concat_iterator for the case of ValueT being a pointer to a common base class to which the result of dereferencing any iterator in ItersT can be casted to.

In particular, the case below was not working before this patch, but I see no particular reason why it shouldn't be supported.

namespace some_namespace {
struct some_struct {
  std::vector&lt;int&gt; data;
  std::string swap_val;
};

struct derives_from_some_struct : some_struct {
};
} // namespace some_namespace

TEST(STLExtrasTest, ConcatRangePtrToDerivedClass) {
  auto S0 = std::make_unique&lt;some_namespace::some_struct&gt;();
  auto S1 = std::make_unique&lt;some_namespace::derives_from_some_struct&gt;();
  SmallVector&lt;some_namespace::some_struct *&gt; V0{S0.get()};
  SmallVector&lt;some_namespace::derives_from_some_struct *&gt; V1{S1.get(), S1.get()};

  // Use concat over ranges of pointers to different (but related) types.
  EXPECT_THAT(concat&lt;some_namespace::some_struct *&gt;(V0, V1),
             ElementsAre(S0.get(),
                         static_cast&lt;some_namespace::some_struct *&gt;(S1.get()),
                         static_cast&lt;some_namespace::some_struct *&gt;(S1.get())));
}

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

2 Files Affected:

  • (modified) llvm/include/llvm/ADT/STLExtras.h (+6-5)
  • (modified) llvm/unittests/ADT/STLExtrasTest.cpp (+16)
diff --git a/llvm/include/llvm/ADT/STLExtras.h b/llvm/include/llvm/ADT/STLExtras.h
index eea06cfb99ba2..951da522a8aa2 100644
--- a/llvm/include/llvm/ADT/STLExtras.h
+++ b/llvm/include/llvm/ADT/STLExtras.h
@@ -1030,14 +1030,15 @@ class concat_iterator
                                   std::forward_iterator_tag, ValueT> {
   using BaseT = typename concat_iterator::iterator_facade_base;
 
-  static constexpr bool ReturnsByValue =
-      !(std::is_reference_v<decltype(*std::declval<IterTs>())> && ...);
+  static constexpr bool ReturnsValueOrPointer =
+      !(std::is_reference_v<decltype(*std::declval<IterTs>())> && ...)
+      || (std::is_pointer_v<IterTs> && ...);
 
   using reference_type =
-      typename std::conditional_t<ReturnsByValue, ValueT, ValueT &>;
+      typename std::conditional_t<ReturnsValueOrPointer, ValueT, ValueT &>;
 
   using handle_type =
-      typename std::conditional_t<ReturnsByValue, std::optional<ValueT>,
+      typename std::conditional_t<ReturnsValueOrPointer, std::optional<ValueT>,
                                   ValueT *>;
 
   /// We store both the current and end iterators for each concatenated
@@ -1088,7 +1089,7 @@ class concat_iterator
     if (Begin == End)
       return {};
 
-    if constexpr (ReturnsByValue)
+    if constexpr (ReturnsValueOrPointer)
       return *Begin;
     else
       return &*Begin;
diff --git a/llvm/unittests/ADT/STLExtrasTest.cpp b/llvm/unittests/ADT/STLExtrasTest.cpp
index 286cfa745fd14..0e6b040a08f4a 100644
--- a/llvm/unittests/ADT/STLExtrasTest.cpp
+++ b/llvm/unittests/ADT/STLExtrasTest.cpp
@@ -398,6 +398,9 @@ struct some_struct {
   std::string swap_val;
 };
 
+struct derives_from_some_struct : some_struct {
+};
+
 std::vector<int>::const_iterator begin(const some_struct &s) {
   return s.data.begin();
 }
@@ -532,6 +535,19 @@ TEST(STLExtrasTest, ConcatRangeADL) {
   EXPECT_THAT(concat<const int>(S0, S1), ElementsAre(1, 2, 3, 4));
 }
 
+TEST(STLExtrasTest, ConcatRangePtrToDerivedClass) {
+  auto S0 = std::make_unique<some_namespace::some_struct>();
+  auto S1 = std::make_unique<some_namespace::derives_from_some_struct>();
+  SmallVector<some_namespace::some_struct *> V0{S0.get()};
+  SmallVector<some_namespace::derives_from_some_struct *> V1{S1.get(), S1.get()};
+
+  // Use concat over ranges of pointers to different (but related) types.
+  EXPECT_THAT(concat<some_namespace::some_struct *>(V0, V1),
+	      ElementsAre(S0.get(),
+			  static_cast<some_namespace::some_struct *>(S1.get()),
+			  static_cast<some_namespace::some_struct *>(S1.get())));
+}
+
 TEST(STLExtrasTest, MakeFirstSecondRangeADL) {
   // Make sure that we use the `begin`/`end` functions from `some_namespace`,
   // using ADL.

Copy link

github-actions bot commented Jun 18, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@jalopezg-git jalopezg-git marked this pull request as draft June 18, 2025 21:49
@jalopezg-git jalopezg-git force-pushed the jalopezg-fix-llvm__concat-related-type branch 6 times, most recently from 3d31e30 to 74331ed Compare June 20, 2025 06:22
@jalopezg-git jalopezg-git marked this pull request as ready for review June 20, 2025 06:24
@jalopezg-git jalopezg-git force-pushed the jalopezg-fix-llvm__concat-related-type branch from 74331ed to ca4ae15 Compare June 20, 2025 06:26
@jalopezg-git jalopezg-git marked this pull request as draft June 20, 2025 07:00
@jalopezg-git jalopezg-git force-pushed the jalopezg-fix-llvm__concat-related-type branch from ca4ae15 to 55726cd Compare June 20, 2025 15:08
Fix llvm::concat_iterator for the case of `ValueT` being a pointer
to a common base class to which the result of dereferencing any
iterator in `ItersT` can be casted to.
@jalopezg-git jalopezg-git force-pushed the jalopezg-fix-llvm__concat-related-type branch from 55726cd to 6af2cf5 Compare June 20, 2025 15:09
@jalopezg-git jalopezg-git marked this pull request as ready for review June 20, 2025 15:41
@jalopezg-git jalopezg-git requested a review from kuhar June 20, 2025 15:49
Comment on lines +1035 to +1045
static constexpr bool ReturnsConvertiblePointer =
std::is_pointer_v<ValueT> &&
(std::is_convertible_v<decltype(*std::declval<IterTs>()), ValueT> && ...);

using reference_type =
typename std::conditional_t<ReturnsByValue, ValueT, ValueT &>;
typename std::conditional_t<ReturnsByValue || ReturnsConvertiblePointer,
ValueT, ValueT &>;

using handle_type =
typename std::conditional_t<ReturnsByValue, std::optional<ValueT>,
ValueT *>;
using handle_type = typename std::conditional_t<
ReturnsConvertiblePointer, ValueT,
std::conditional_t<ReturnsByValue, std::optional<ValueT>, ValueT *>>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is getting complicated with the need for a nested condition -- could we move this to a new type trait struct or find some other way to simplify the code?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants