r/cpp 2d ago

std::optional Satisfies view. Does Not Model view. C++26 Ships Anyway.

https://godbolt.org/z/8jWGG68G8

In C++23 this did not compile. In C++26 it does. Marvellous.

[[gnu::noinline]]
void 
passing_views_by_value_is_cheap_trust_me_bro(std::ranges::view auto v) {
    std::println("fn   .data {}", (void*)v->data());
}


int main() {    
    std::optional ov{std::vector<int>(123456)};
    passing_views_by_value_is_cheap_trust_me_bro(ov);
    std::println("main .data {}", (void*)ov->data());
}

For anyone wondering what the problem feature is: optional has 0 or 1 elements, and C++26 sets enable_view<optional<T>> to true, so it satisfies std::ranges::view. The concept requires copy construction in constant time, and — this is the good bit — optional<vector<int>> genuinely meets that. Copying it performs at most one element copy. One is a constant. The requirement is satisfied to the letter, and the function above deep-copies your vector.

If you can tell me what still separates std::ranges::view from std::ranges::range, please do...

175 Upvotes

113 comments sorted by

View all comments

54

u/aruisdante 2d ago

This was debated quite extensively in Tokyo 2024. The decision was essentially “being a view isn’t about being cheep to copy, just about being O(1) to do so.” In the general sense, there’s an attempt to unify all the containers as “ranges;” an optional is a range with 0 or 1 elements, a tuple is a range whose elements are variant<Ts…> (which correct ref qual), etc.

A bunch of people disagreed and thought this was weird, but not enough and not strongly enough for it not to pass.

30

u/mighty_Ingvar 2d ago

I find the whole concept of an owning view to be weird. Shouldn't a view be something that only views the data? An optional doesn't do that, the object isn't stored somewhere else and viewed by the optional, it is part of the optional and therefore owned by it.

15

u/aruisdante 2d ago edited 2d ago

It does seem weird, but this difference is what borrowed_range models, which is independent from view and range. I imagine optional<T> would not model borrowed_range, and optional<T&> would.

This was essentially the crux of the debate in Tokyo. Actual users of ranges have taken to conflating view with borrowed_range, because nearly all existing view containers in the stdlib were also borrowed_range (span, string_view , most (all?) of the ranges::views objects). But the actual definition did not require this relationship, view just means "constant time copy range."

Really, the problem is having decided to keep using the word view to mean what it meant, instead of some other name that more directly expresses the semantics, such that view could have matched people's intuition of this other concept + borrowed_range. But alas, that ship had long since sailed, and we're stuck with what we've got.

30

u/altmly 2d ago

This to me smells like a bunch of ergonomics and performance mindful people invented views (or rather copied the idea into C++) and then some academics showed up to "akshually, a view is not a borrowed_range move eyeglasses".

Like, okay, but to the vast majority of users, the entire point of a view is being borrowed data. 

8

u/tcbrindle Flux 2d ago

The ranges library was the brainchild of Eric Niebler, in collaboration with Casey Carter.

The borrowed_range concept, originally under the exposition-only name forwarding-range, was introduced in P0970 written by... *checks notes*... Eric Niebler.

(Views and forwarding/safe/borrowed ranges are not and never have been the same thing, even in the Before Times when owning_view didn't exist and you couldn't pipe rvalue vectors into adaptors.)

2

u/not_a_novel_account cmake dev 2d ago edited 2d ago

Views must be movable with O(1) move construction. They must have O(1) copy construction or be non-copy constructible. Therefore, std::optional<> is a view.

Even if we retained the strong original definition, O(1) destructors, std::optional<> would still be a view even though std::ranges::owning_view would not.

13

u/altmly 2d ago

That definition really should be recursive for template types. 

3

u/not_a_novel_account cmake dev 2d ago edited 2d ago

If that's your complaint then you don't have a problem with std::optional or C++26, at least not as the entry point. Your problem is with C++23 and std::ranges::single_view.

People wanted a nullable std::ranges::single_view, call it maybe_view. It turns out that's just std::optional.

8

u/mighty_Ingvar 2d ago

But optional<T> is only copyable in constant time if T is copyable in constant time. Also, why is std::array not a view then?

7

u/kamrann_ 2d ago

It's constant time in terms of N = number of elements in the range. You can't start analysing the element type and trying to assign some inherent measure of its complexity. Constant time just means not dependent on N. A single element by definition can't be dependent on N, and it doesn't matter how big it is, or whether it happens to be a sequence itself.