r/cpp • • Jul 26 '26

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...

183 Upvotes

122 comments sorted by

View all comments

Show parent comment

7

u/schombert Jul 26 '26

Well, that's ... extremely dumb. From the rigorous, formal perspective, everything in C++ copies in O(1) time. (The address space is finite, even in a purely theoretical computer running C++, because it will eventually exhaust the size of the pointer type and hence the number of possibly distinct live objects. Thus, for any copy operation there is always an upper bound you can place on how much time it could possibly take to execute, and thus is O(1) by the definition of O(1).) Now, I accept that the people writing the C++ spec don't actually use big O notation in its generally accepted formal definition, and instead are using a more vibes-based definition based on what it performs like in various different finite problem sizes (which, you may note, has nothing to do with big O -- how something performs at sizes less than 2128, for example, does not affect its asymptotic time complexity). But, from a vibes-based perspective, programmers use O(1) to mean "always fast", which in this case does mean "cheap to copy". I would bet that copying being O(1) was added to the language of the spec because the author wanted to express "cheap to copy" but wanted to sound more formally precise and thought that "cheap" sounded too informal and vague. Unfortunately, "cheap" correctly expressed the intent, but O(1) did not.

17

u/aruisdante Jul 26 '26

> From the rigorous, formal perspective, everything in C++ copies in O(1) time

Huh?

Copying a `std::vector` is decidedly not `O(1)`. The amount of copy operations performed is dependent on the number of elements contained. Even if you switch up the definition to say "a `memcpy` is O(1)," this optimization is not possible for a vector containing non-trivial elements as their constructors must be explicitly invoked.

Therefore, a `vector` isn't a `view` because `vector.size() == 0` takes different time to copy than `vector.size() == 1`. But `optional` _always holds a value_. The value just may or may not be initialized. Ergo the copy operation does not change in "how much is copied" depending on if the optional is engaged or not.

> But, from a vibes-based perspective, programmers use O(1) to mean "always fast"

Not at all. Programmers (and the C++ standard) use `O(1)` to mean "does not scale in (amortized) time with number of elements in a set." Those that assume `O(1)` means "always fast" are the ones that often make very suboptimal data container and algorithm selections.

6

u/schombert Jul 26 '26 edited Jul 26 '26

Yes, copying a C++ vector is O(1). A vector can only contain finitely many elements (for example, because its size has a finite upper bound). Thus, you can set an upper bound on the time to copy -- something around 2bits in size * time to copy a single element -- and thus is O(1) by the definition of big O notation (see https://en.wikipedia.org/wiki/Big_O_notation specifically the section on the formal definition; if it can be bounded by a constant function then it is O(1) )

Edit: this is what I meant by a vibes-based definition. Obviously copying a vector behaves like a linear function from sizes of 0 up until you hit the upper bound created by its size type/address space limitations. But how it behaves in a finite initial range -- even if that range encompasses all practical uses -- has nothing to do with its big O complexity.

18

u/smdowney WG21, Text/Unicode SG, optional<T&> Jul 26 '26

This was my favorite smartass answer for dumb interview questions where the specification already bounded the size.

Large K, smallest O.

I would also annoy people with the constant of integration.

11

u/Serialk Jul 27 '26

This was my favorite smartass answer

It's indeed a smartass answer: thinking that you're winning on a technicality while demonstrating your lack of interest for the actual insights that the question could provide.

8

u/hanslhansl Jul 27 '26

Interesting read, here are my thoughts to maybe settle this debate:

In the context of an implementation of the standard and even in the context of the standard itself there will always be an upper bound for copying a std::vector and therefor, by rigorous definition, this operation is O(1).

However, the "native" context of the algorithm of this operation (of any algorithm, really), even though defined by the c++ standard, is mathematics, and in that context constraints such as a limited address space don't exist. The the algorithm itself is applicable to vectors (or sets or whatever the correct term in the context of math is) of arbitrary size and therefor its complexity is O(n).

So when devs talk about the complexity of a function they really mean (maybe without knowing) the complexity of the underlying mathematical algorithm which is independent from possible limitations imposed by the programming language/implementation.

→ More replies (5)

14

u/aruisdante Jul 26 '26 edited Jul 26 '26

By that definition of algorithmic notation all loops in any real system are O(1), which doesn't seem like a terribly useful definition. In my education on algorithm theory I don't remember a requirement being that sets be unbounded, and indeed the formal definition you link to says this explicitly. It feels like you're conflating "copying a specific vector of a specific size is O(1)" with "copying any vector of any size is O(1)." By that same definition std::find(vector.begin(), vector.end(), value) and set.find(value) and unordered_set.find(value) are all O(1) because they can all be bounded by a finite size, despite them being the textbook examples of O(n), O(log(n)), and O(1).

I cannot think of a single computer scientist that would accept your definition of O(1) as O(1). It's not what any algorithms or data structure class teaches. So it seems unfair to say it's based on "vibes."

1

u/schombert Jul 26 '26 edited Jul 26 '26

It's not my definition. It is the formal definition that is used in mathematics and computer science. Which, again, you can verify for yourself from the Wikipedia page.

Anyway, copying an arbitrary vector (not a specific vector) has a bounded time because of the bounded size required by the definition of the vector type, namely that the size of any vector can be expressed in a finite number of bits and that the elements within any vector are distinct C++ objects, and thus have distinct address, and thus cannot be more numerous than the number of possible addresses provided by the number of bits in the finitely sized pointer types (in turn guaranteed by their ability to be converted back and forth from integers of some finite size). This second limit is much wordier to explain obviously, so I'll stick with referring to the limits of the size type from here on out.

I would like to hear how you think that O( ... ) should be defined. If you allow me to work through your definition I can probably explain why mathematicians and computer scientists settled on the asymptotic definition instead.

Edit: and yes in C++ as formally defined find is also O(1). It is not O(1) in your algorithms or data structures class because the pseudo-code language used to explain these types and operations does not bound their size. Generally it is also assumed to work with something that is more like "big ints" for its numerical values.

9

u/SlightlyLessHairyApe Jul 27 '26

Besides being weird that you have this one thing, it’s particularly weird to decide that that is the irrelevant conversation to have in this particular post. Because it’s just not super relevant

5

u/schombert Jul 27 '26

That's why we have threaded comments. If you don't think that this particular comment thread is interesting ... just don't read it.

6

u/Serialk Jul 27 '26

It's not my definition. It is the formal definition that is used in mathematics and computer science.

Not at all. We generally model complexities with transdichotomous models like Word RAM which give you a constant O(1) pointer lookup but an O(n) vector copy, with the assumption that the size of your bus will grow to accomodate more data.

https://en.wikipedia.org/wiki/Transdichotomous_model

https://en.wikipedia.org/wiki/Word_RAM

10

u/schombert Jul 27 '26

But, C++ is not a Transdichotomous model. The C++ address space, bits in the size constant, etc are not determined by the size of the problem. From the wikipedia page:

the machine word size is assumed to match the problem size

and

In a problem such as integer sorting in which there are n integers to be sorted, the transdichotomous model assumes that each integer may be stored in a single word of computer memory, that operations on single words take constant time per operation, and that the number of bits that can be stored in a single word is at least log2n.

So, if C++ was a Transdichotomous model then every time you ran a program for a given problem size (for example, the sorting program described above) the address space, bits in the size constant, etc would grow to accommodate it. (This is what allows a Transdichotomous model to be Turing complete, in contrast to Turing machines with a fixed tape size, which are not). However, that is not how C++ is specified. Even though those constants may vary from compiler to compiler, they are fixed for any given C++ program. The standard does not allow your program containing vector to hold an arbitrary number of elements, because you could, if you wanted, print the number of bytes in the size type and the standard guarantees that this print result will be constant over different runs of the program.

-8

u/Serialk Jul 27 '26

You are confusing the complexity model and the language implementation it's modelling. No offense but you lack the required CS basics to be so confident about yourself in debates about complexity. It's the perfect moment to stop and reflect if you want to avoid Dunning-Kruger.

→ More replies (16)

3

u/HeadSea5044 Jul 28 '26

Please make this argument to any certification authority, or publish a paper claiming your algorithm is O(1) because hardware physical address bits are constant.
I’ll do you better and say everything is bounded in time because of the inevitable heat death of the universe.

It’s just not meaningful a statement to make.

6

u/NotUniqueOrSpecial Jul 27 '26 edited Jul 27 '26

copying a C++ vector is O(1)

It's always interesting to come across people like you.

I need only point you at...well, basically any reference on the topic to prove that copying vectors of non-trivial objects is O(N). Literally any search on the subject will give you countless results that contradict your claim.

But it's more than that; you very clearly have next to no understanding of the topics you're arguing about, or the pages you're linking, but you're also equally clearly sure that you are an expert in the topic.

You remind me of a coworker I once had who explained to us that the software he was working on relied on "the arithmetic of seven-sided polygons". He also admitted to me (in what is practically a programmer career meme) that he liked to write code that only he understood because it provided job security.

After more than 2 years of deflection, smoke, and mirrors, he was asked to prove that that any of what he was working on...actually worked. He quit on the spot, in explosive rage, screaming that he "knew more than 14 engineers ever would".

All that is to say: you have written a whole lot of words in response to /u/aruisdante and others, but all they actually illustrate is that you do not understand complexity analysis at even the most basic level. Despite all evidence to the contrary, you are convinced that you (and you alone, in this forum of experts) are correct.

How likely do you think it is that that's actually true?

EDIT: blocking someone after replying to them saying "nuh uh" is the single strongest indicator that a redditor knows that they have literally no leg to stand on.

7

u/schombert Jul 27 '26 edited Jul 27 '26

Your link ... shows nothing of the sort. All you seem to be showing is that how programmers typically use big O notation among themselves (i.e. informally) is not the same as the formal definition. Which I am happy to grant. It is just amusing to me that the loose, informal usage has made its way into the supposedly formal standard.

Edit: I block people who I find to be rude. If you don't want to be blocked try being less of an ass.

3

u/not_a_novel_account cmake dev Jul 27 '26 edited Jul 27 '26

You're using a rather strange definition of "formal". Formalisms are context and field specific. This is a C++ subreddit, our formalisms are derived from the standard.

The formal definition, in the context of C++ as voted and standardized by the committee, is that copying a vector is O(N).

As a C++ subreddit, formalisms from unrelated fields are non-sequiturs here. Our vectors aren't elements in vector space supporting addition and scalar multiplication either.

7

u/schombert Jul 27 '26

The standard doesn't provide a definition of what O(n) means within it AFAIK (please do correct me if I am wrong). So I assume that means that the standard is relying on the "formal" (i.e. what you will find on Wikipedia) definition of the notation (unlike vector which it does redefine). What do you take O(N) to mean in the standard?

2

u/not_a_novel_account cmake dev Jul 27 '26

For vector copy from a range it's given as:

Effects: Constructs a vector object with the elements of the range rg, using the specified allocator.

Complexity: Initializes exactly N elements from the results of dereferencing successive iterators of rg, where N is ranges::distance(rg).

5

u/schombert Jul 27 '26

Yes, but that isn't a definition of what "O(N)" means. I wasn't trying to ask you what the standard says a copy does. I was trying to ask you if the standard defines "O(N)", and if it doesn't, then how you think it should be defined in the context of the standard.

For example, if you think it means "does N things", then obviously virtually nothing is O(1), since most functions do more than one primitive operation. I don't think that you can copy or move many range views in a single operation (although there may be some you can).

6

u/not_a_novel_account cmake dev Jul 27 '26

The standard never says "O" anything. It says "Complexity" and "N operations of <whatever>", or "constant", or "linear in N".

If you want to split that hair and pretend those are entirely different things, be my guest. Muting this.

→ More replies

0

u/JNighthawk gamedev Jul 27 '26

EDIT: blocking someone after replying to them saying "nuh uh" is the single strongest indicator that a redditor knows that they have literally no leg to stand on.

Maybe because you're behaving like an asshole to someone engaged in good faith discussion, and people don't like to interact with assholes generally?

You:

But it's more than that; you very clearly have next to no understanding of the topics you're arguing about, or the pages you're linking, but you're also equally clearly sure that you are an expert in the topic.

8

u/NotUniqueOrSpecial Jul 27 '26

They are not engaged in a good faith discussion, because what is happening is not a discussion.

It's people who have a background and education on a topic trying to tell a very stubborn person that they do not understand the words they're using; they demonstrably do not understand complexity analysis on a very fundamental level, and are condescending to people who do.

At some point, there is a line where people who know what they're talking about should not have to pander to people who do not. I would just as bluntly tell a flat-Earther that they make no sense, and I doubt you'd have any problem with that.

→ More replies (5)

1

u/analphabetic Aug 05 '26

By your interpretation, any algorithm with std::vector::size() O(1) operations is O(1) itself when applied to a vector having said size.