Skip to content

Conversation

@localspook
Copy link
Contributor

@localspook localspook commented Dec 7, 2025

See the doc comment for how it works.

I'm planning to use this in an upcoming refactor of this function. Here's another, completely unrelated function it could simplify; this seems like a generally useful feature.

@llvmbot
Copy link
Member

llvmbot commented Dec 7, 2025

@llvm/pr-subscribers-llvm-adt

Author: Victor Chernyakin (localspook)

Changes

See the doc comment for what it does.

I'm planning to use this in an upcoming refactor of this function. Here's another, completely unrelated function it could simplify; this seems like a generally useful feature.


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

2 Files Affected:

  • (modified) llvm/include/llvm/ADT/STLExtras.h (+24)
  • (modified) llvm/unittests/ADT/STLExtrasTest.cpp (+9)
diff --git a/llvm/include/llvm/ADT/STLExtras.h b/llvm/include/llvm/ADT/STLExtras.h
index af0e4a36be1b1..4645b35b234fb 100644
--- a/llvm/include/llvm/ADT/STLExtras.h
+++ b/llvm/include/llvm/ADT/STLExtras.h
@@ -2610,6 +2610,30 @@ template <typename T> using has_sizeof = decltype(sizeof(T));
 template <typename T>
 constexpr bool is_incomplete_v = !is_detected<detail::has_sizeof, T>::value;
 
+/// A utility for working with maps that allows concisely expressing "perform
+/// and cache this expensive computation only if it isn't already cached".
+/// Use it like so:
+///
+/// ```cpp
+/// std::unordered_map<K, V> Cache;
+/// auto& Value = Cache.try_emplace(
+///     Key, llvm::defer {[] { /* heavy work */ }}).first->second;
+/// ```
+template <typename FnT> class defer {
+public:
+  constexpr defer(FnT &&Fn LLVM_LIFETIME_BOUND) : Fn(Fn) {}
+
+  template <typename U> constexpr operator U() {
+    return std::forward<FnT>(Fn)();
+  }
+
+private:
+  FnT &Fn;
+};
+
+// Silence -Wctad-maybe-unsupported.
+template <typename FnT> defer(FnT &&Fn LLVM_LIFETIME_BOUND) -> defer<FnT>;
+
 } // end namespace llvm
 
 namespace std {
diff --git a/llvm/unittests/ADT/STLExtrasTest.cpp b/llvm/unittests/ADT/STLExtrasTest.cpp
index 85567775e4ebd..18a78e625022c 100644
--- a/llvm/unittests/ADT/STLExtrasTest.cpp
+++ b/llvm/unittests/ADT/STLExtrasTest.cpp
@@ -1699,4 +1699,13 @@ struct Bar {};
 static_assert(is_incomplete_v<Foo>, "Foo is incomplete");
 static_assert(!is_incomplete_v<Bar>, "Bar is defined");
 
+TEST(STLExtrasTest, Defer) {
+  std::unordered_map<int, int> Cache;
+  const auto [It, Inserted]{Cache.try_emplace(1, defer{[] { return 10; }})};
+  ASSERT_TRUE(Inserted);
+  ASSERT_EQ(It->second, 10);
+  Cache.try_emplace(
+      1, defer{[] { ASSERT_TRUE(false && "this should never be executed"); }});
+}
+
 } // namespace

@localspook localspook changed the title [LLVM] Add helper class for working with caches [LLVM][ADT] Add helper class for only Dec 7, 2025
@localspook localspook changed the title [LLVM][ADT] Add helper class for only [LLVM][ADT] Add helper class for working with caches Dec 7, 2025
@github-actions
Copy link

github-actions bot commented Dec 7, 2025

🐧 Linux x64 Test Results

  • 187093 tests passed
  • 4924 tests skipped

✅ The build succeeded and all tests passed.

@zwuis
Copy link
Contributor

zwuis commented Dec 7, 2025

An idea about naming: "elide".

https://wg21.link/p3288

@localspook
Copy link
Contributor Author

localspook commented Dec 7, 2025

Oh that's interesting, yeah, this defer is basically a simplified version of that paper's elide. Which is interesting, since we seem to have completely different use cases in mind! The paper calls it elide because it only seems concerned with enabling copy elision. My use case doesn't involve any elision, I'm only concerned with deferring the computation until it's needed, so I called it defer. I feel like neither of those names captures both use cases...

Comment on lines +2619 to +2620
/// auto& Value = Cache.try_emplace(
/// Key, llvm::defer {[] { /* heavy work */ }}).first->second;
Copy link
Member

Choose a reason for hiding this comment

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

I don't get it -- how is this different than passing the lambda directly?

Copy link
Member

Choose a reason for hiding this comment

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

Oh, I see, it's invoked through the conversion operator. I'd definitely focus on this aspect in the explanation of what this does, in abstract of the intended use in emplace operations

Comment on lines +1702 to +1711
TEST(STLExtrasTest, Defer) {
std::unordered_map<int, int> Cache;
const auto [It, Inserted]{Cache.try_emplace(1, defer{[] { return 10; }})};
ASSERT_TRUE(Inserted);
ASSERT_EQ(It->second, 10);
Cache.try_emplace(1, defer{[] {
assert(false && "this should never be executed");
return 0;
}});
}
Copy link
Member

Choose a reason for hiding this comment

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

This would benefit from tests without unordered_map.

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.

4 participants