Graph views in graph-v3 support full C++20 range chaining with std::views, including
when using value functions. This was achieved by changing value function signatures to receive the
graph as a parameter rather than requiring lambda capture.
Value functions now receive the graph as a parameter instead of requiring capture:
// Graph is passed as a parameter — lambda is stateless
auto vvf = [](const auto& g, auto v) { return vertex_id(g, v) * 10; };
// Full chaining support!
auto view = g | vertexlist(vvf)
| std::views::take(2); // ✅ Works perfectly!
| std::views::filter(...); // ✅ Chains freely!Stateless lambdas (empty capture list []) are:
- Default constructible: No captured state to initialize
- Copyable: No captured references to worry about
- Semiregular: Satisfies
std::semiregularconcept
These properties allow views storing stateless lambdas to satisfy std::ranges::view,
which is what std::views adaptors require.
auto vvf = [](const auto& g, auto v) { return vertex_id(g, v); };
using ViewType = vertexlist_view<G, decltype(vvf)>;
std::ranges::view<ViewType> // ✅ true
std::ranges::range<ViewType> // ✅ true
std::ranges::input_range<ViewType> // ✅ true
std::semiregular<ViewType> // ✅ true
std::default_initializable<ViewType> // ✅ trueauto vvf = [](const auto& g, auto v) { return vertex_id(g, v) * 10; };
auto view = g | vertexlist(vvf)
| std::views::take(2); // ✅ Works!auto view = g | vertexlist() // No value function
| std::views::transform([&g](auto vi) {
auto [v] = vi;
return vertex_id(g, v) * 10;
})
| std::views::take(2); // ✅ WorksNote: Capturing lambdas are fine inside std::views::transform and other standard
adaptors — only the graph view itself needs to be semiregular.
auto vvf = [](const auto& g, auto v) { return vertex_id(g, v); };
auto view = g | vertices_dfs(0, vvf)
| std::views::take(5); // ✅ Works!
auto evf = [](const auto& g, auto e) { return target_id(g, e); };
auto edges = g | edges_bfs(0, evf)
| std::views::take(3); // ✅ Works!Previously, value functions took only a descriptor:
- VVF:
vvf(v)— vertex descriptor only - EVF:
evf(e)— edge descriptor only
This required users to capture the graph reference:
// OLD pattern — capturing lambda breaks chaining
auto vvf = [&g](auto v) { return vertex_id(g, v) * 10; };
// ^^^^ Capture makes lambda statefulLambdas with captures are NOT default constructible:
int data = 42;
auto lambda = [data](int x) { return data + x; };
// This fails:
decltype(lambda) default_lambda; // ❌ Error: no default constructorWhy? The compiler doesn't know what value to give the captured variable data when
default-constructing.
When a view stored a capturing lambda as its VVF:
- The view inherited the lambda's properties
- The view became not default_initializable
- Therefore, the view didn't satisfy
std::ranges::view std::viewsadaptors refused to work with it
// OLD: This FAILED to compile
auto vvf = [&g](auto v) { return vertex_id(g, v) * 10; };
auto view = g | vertexlist(vvf)
| std::views::take(2); // ❌ Compilation errorValue functions now receive the graph as a first parameter:
- VVF:
vvf(g, v)— graph and vertex descriptor - EVF:
evf(g, e)— graph and edge descriptor
The view passes std::as_const(*g_) as the graph argument, ensuring const-correctness.
Since the graph is a parameter rather than a capture, the lambda remains stateless
and the view satisfies std::ranges::view.
C++26 will introduce std::copyable_function (P2548) which could also solve this problem
by wrapping capturing lambdas in a semiregular type-erased wrapper. However, the parameter-based
approach adopted here:
- Works in C++20 today (no C++26 dependency)
- Is more explicit about graph access
- Avoids type-erasure overhead
- Enables more flexible value functions
| Pattern | Chaining | Captures | Semiregular | Works? |
|---|---|---|---|---|
vertexlist() (no VVF) |
Yes | N/A | ✅ Yes | ✅ Yes |
vertexlist(stateless_vvf) |
Yes | No | ✅ Yes | ✅ Yes |
vertexlist(capturing_vvf) |
No | Yes | ❌ No | ❌ No |
Note: With parameter-based value functions, the typical usage is always the "stateless" row. Capturing lambdas would only occur if a user intentionally captured additional non-graph state.