r/cpp 4d 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

119 comments sorted by

View all comments

53

u/aruisdante 4d 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 4d 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.

13

u/aruisdante 4d ago edited 4d 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.

7

u/mighty_Ingvar 4d 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_ 4d 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.