view_interface
types are boolean-testable
view_interface
types are boolean-testableToday I learned:
std::ranges::view_interface
provides an explicit operator bool
. When you have a view type from the Ranges STL
(such as std::ranges::subrange
), you can branch on its boolean value: empty
ranges are falsey and everything else is truthy.
namespace rv = std::views;
std::vector<int> v = {1,2,3,4,5};
auto isOdd = [](int x) { return (x % 2) != 0; };
if (v | rv::filter(isOdd)) {
puts("v contains at least one odd number");
}
A common stumbling block for Ranges-learners (in which I still include myself)
is that the Ranges version of remove_if
returns a subrange
instead of
just an iterator… but the range it returns isn’t the new range of valuable
elements, it’s the range of trailing junk elements!
Since views are boolean-testable, and subrange
is a view type,
testing the return value of remove_if
tells you whether any elements
were removed:
if (auto rr = std::ranges::remove_if(v, isOdd)) {
puts("I just removed at least one odd number from v");
v.erase(rr.begin(), rr.end());
}
Using STL Classic, you’d have had to write a separate test it != v.end()
after the call to remove_if
, like this:
if (auto it = std::remove_if(v.begin(), v.end(), isOdd); it != v.end()) {
puts("I just removed at least one odd number from v");
v.erase(it, v.end());
}
This is probably not the primary reason that Ranges algorithms tend to return
ranges instead of iterators. In fact, honestly, please don’t use this in your codebase!
But it’s still kind of nifty to know about, and it might be a useful mnemonic
for the return values of certain Ranges algorithms (such as remove_if
and unique
).
The
if (auto x=y; z)
syntax is new in C++17: see “while (auto x=y; z)
” (2020-10-28). And note that in both snippets the second argument tov.erase
is critically important: “How to erase from an STL container” (2020-07-08).
This nifty trick relates only to views that inherit their operator bool
from view_interface
— not to arbitrary ranges such as vector<int>
,
and not even to arbitrary view types such as string_view
or span
.
But it does apply to subrange
, and also to many types returned from range
adaptor pipelines. I suppose this is where I mention that thanks to C++20’s
P1252
and P1739,
even most range adaptors don’t always return view_interface
types…
if (auto r = "foo"sv | rv::reverse) // OK, r is reverse_view<string_view>
if (auto r = "foo"sv | rv::reverse | rv::reverse) // Error, r is string_view
if (auto r = v | rv::drop(1)) // OK, r is drop_view<ref_view<vector<int>>>
if (auto r = std::span(v) | rv::drop(1)) // Error, r is span<int>
But that’s a story for another day.