On Fri, 9 Aug 2024 16:05:00 GMT, Gerard Ziemski <gziem...@openjdk.org> wrote:
> > > Is there a concrete advantage here for using lambda expression when > > > iterating committed regions? I'm asking because personally I find > > > ```c++ > > > while ((committed_rgn = itr.next()) != nullptr) { > > > print_committed_rgn(committed_rgn); > > > } > > > ``` > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > simpler and more compact, hence easier to understand, than > > > ```c++ > > > CommittedMemoryRegion cmr; > > > > > > VirtualMemoryTrackerWithTree::tree()->visit_committed_regions(reserved_rgn, > > > &cmr, [&](CommittedMemoryRegion* crgn) { > > > print_committed_rgn(crgn); > > > return true; > > > }); > > > ``` > > > > > > Hi Gerard, I'm the one who prefers passing in a lambda and introduced that > > style, sorry :-). > > First off, I think the lambda code should be simplified, it should be: > > ```c++ > > VMWT::tree().visit_committed_regions_of(*reserved_rgn, [](const > > CommittedMemoryRegion& crgn) { > > print_committed_region(crgn)); > > return true; > > }); > > ``` > > > > > > > > > > > > > > > > > > > > > > > > I don't think that's too bad, right? The `return true` is a bit > > unfortunate. It's for being able to stop early, which I agree that regular > > iterators do better by just performing a `break;`. > > I'll go ahead and say one nice thing too: If the body of the lambda is a > > bit more complicated, then we can look at the capture list (the `[]` above) > > to see what the lambda can alter in the outside scope. With a while-loop, > > you really have to read the whole thing. > > **The reason** that I write these kinds of iterators is that they're much > > simpler to implement and maintain and, _to me_, STL-style iterators aren't > > easier to read, it's about the same level of complexity. > > I'll admit that your style of iterators (which are _not_ STL) is about the > > same complexity, though I find it unfortunate that we have to write an > > entire class for each iterator. > > With the simplifications I made, is this style of iterator acceptable? > > hi Johan, > > Yes, this does look better. I looked at > https://www.geeksforgeeks.org/when-to-use-lambda-expressions-instead-of-functions-in-cpp > to see when one should consider using lambda and I like one particular > scenario - improving readability by implementing the code locally, so one can > see exactly what is going on without having to look inside a function. I > think this is our use case scenario here. > > If only we didn't need all those `return` statements, I'd have clearly > preferred lambda here in fact. It's tedious to have to write `return true;` if you never `return false;` in the function. I looked into fixing this with some metaprogramming, and I found two C++14 solutions and one C++17 one. Here's the Godbolt: https://godbolt.org/z/xGzoGYhf8 And here's the code, reproduced for posterity: ```c++ #include <iostream> #include <vector> // C++14 solution // Here we take extra runtime cost for the void returning function #define ENABLE_IF(...) \ std::enable_if_t<bool(__VA_ARGS__), int> = 0 template <typename F, ENABLE_IF(std::is_same<bool, typename std::result_of<F(int &)>::type>::value)> void iterate(std::vector<int>& arr, F fun) { for (int i = 0; i < arr.size(); i++) { if (!fun(arr[i])) { return; } } } template<typename F, ENABLE_IF(std::is_void<typename std::result_of<F(int&)>::type>::value)> void iterate(std::vector<int>& arr, F fun) { iterate(arr, [&fun](int& i) { fun(i); return true; }); } // C++17 solution, notice that this compiles two functions: one for when early_return is true and when for when it's not. /* template<typename F> void iterate(std::vector<int>& arr, F fun) { using ret_type = decltype(fun(arr[0])); // This doesn't actually execute fun(arr[0]); constexpr bool early_return = std::is_same<ret_type, bool>::value; for (int i = 0; i < arr.size(); i++) { if constexpr (early_return) { if (!fun(arr[i])) { return; } } else { fun(arr[i]); } } } */ // C++14 solution // Here we have to make the two implementations ourselves. /* #define ENABLE_IF(...) \ std::enable_if_t<bool(__VA_ARGS__), int> = 0 template <typename F, ENABLE_IF(std::is_same<bool, typename std::result_of<F(int &)>::type>::value)> void iterate(std::vector<int>& arr, F fun) { for (int i = 0; i < arr.size(); i++) { if (!fun(arr[i])) { return; } } } template<typename F, ENABLE_IF(std::is_void<typename std::result_of<F(int&)>::type>::value)> void iterate(std::vector<int>& arr, F fun) { for (int i = 0; i < arr.size(); i++) { fun(arr[i]); } } */ int main() { std::vector<int> my_array{0,1,2,3}; auto no_early = [](int& x) { std::cout << x << " "; }; auto early = [](int &x) { if (x == 2) return false; std::cout << x << " "; return true; }; iterate(my_array, no_early); std::cout << std::endl; iterate(my_array, early); std::cout << std::endl; } > I honestly do not think we should be checking in 2 implementations, unless > they are nicely hidden away. I do not like the way we manage them right now > with 2 explicit `if` checks, each and every time we need to do something. > > If you could push the 2 implementations into a factory class, so that instead > of: > > ``` > if (is_using_sorted_link_list()) > VirtualMemoryTracker::snapshot_thread_stacks(); > if (is_using_tree()) > VirtualMemoryTrackerWithTree::Instance::snapshot_thread_stacks(); > ``` > > we have just: > > ` VirtualMemoryTracker::snapshot_thread_stacks();` and > > ``` > VirtualMemoryTracker::VirtualMemoryTracker(bool useLinkedList) { > if (useLinkedList) { > instance = new VirtualMemoryTrackerWithLinkedList() > } else { > instance = new VirtualMemoryTrackerWithLinkedList() > } > } > > VirtualMemoryTracker::snapshot_thread_stacks() { > instance.snapshot_thread_stacks(); > } > ``` >From reading the code, this might be a bit harder to do than preferred. We'd >need some wrapping for the iterators, since they use two different styles. >Wrapping the "old" iterators inside of the "new" style is possible of course. The fork for JDK-24 is on December 5th. That means that we have at most 8 weeks in the testing system to find and fix any bugs that we might have missed after integration. To me, that feels a bit short. Either we wait after the fork to integrate, or we integrate with two implementations and a `java` option for choosing implementation. I'm open to other opinions. ------------- PR Comment: https://git.openjdk.org/jdk/pull/20425#issuecomment-2309938825 PR Comment: https://git.openjdk.org/jdk/pull/20425#issuecomment-2378890343