> It is fundamental tenet of C's philosophy that the programmer is always right, even when they are wrong.
That's not really the choice we're faced with though. Ada and Rust are both far safer than C, and they're also plenty powerful.
> by not deleting
I think there's a problem here.
C++ fails at this pretty spectacularly.
There is a problem indeed, which is OP's failure to implement exception-safe code.
If someone opts to use exceptions, they have the responsibility of writing their code in a way that complies with scenarios involving throwing exceptions.
Failing to support a scenario by not following the most basic principles is something that's on the person writing the code, not the language.
It would make as much sense to blame java for the problems you create by not initializing objects in some code paths, because your code started to throw null pointer exceptions.
auto fooRef = myWidget.getRef(); vs. auto& fooRef = myWidget.getRef();?
That's not a problem caused by exceptions.
If you're talking about the article, the standards committee seems to think it's a confusing interpretation that never should have existed, and it's pretty ridiculous to say it's "basic principles".
I mean in hindsight it's obvious, but still not exactly what I wanted to happen.
For reference, what I did does actually work, temporarily.
In standard terminology, this is described as "invalidating iterators". There are a bunch of member functions in std::vector that either do or don't invalidate iterators, e.g. push_back(...) does but size() doesn't. And as the name implies, if you call a function that invalidates iterators, all your existing iterators/pointers/references become invalid.
Some containers guarantee references won't dangle on mutation such as unordered_map. An unordered_map may invalidate iterators if objects are added or removed, but will never result in dangling references (unless the object is removed). That is, it is safe to have a pointer to an object owned by an unordered_map and continue using that pointer even after an iterator to that same object is invalidated.
I mean, literally. The iterator of an std::vector<T> (not a bool vector) is a T*.
static_assert(std::is_same_v<T\*, std::vector<T>::iterator>)
Link provided below for convenience [1].Furthermore just like a cat is animal does not imply an animal is a cat, a pointer being an iterator does not imply that an iterator is a pointer and holding such a confusing thought is the source of many bugs and poor C++ code. An iterator not only represents a form of indirection to an object, it also represents traversal through a collection of values.
unordered_map guarantees that the objects it stores will remain alive until the unordered_map is destroyed or the object is removed from the map. However, unordered_map, as the name once again suggests, does not provide any guarantee about the ordering of objects. Hence, if you have an iterator to an object owned by such a map, and you add or remove objects to that map, the iterator becomes invalid not because the object the iterator references gets destroyed, but because the iterator loses information about where it's located within the unordered collection.
For further information, please review the following link [2], specifically the quote "References and pointers to either key or data stored in the container are only invalidated by erasing that element, even when the corresponding iterator is invalidated.".
[1] https://godbolt.org/z/Y5f4d74Kb
[2] https://en.cppreference.com/w/cpp/container/unordered_map
And an animal is a generalization of a cat, but that doesn't mean an animal is a cat. What is true of a particular doesn't have to be true of the general.
> But, while implementation defined, a vector iterator must behave identically to a pointer
No it does not. MSVC implements a vector iterator with additional runtime safety guarantees in DEBUG mode and while those guarantees are not mandatory as per the standard, they are fully in compliance, so saying they have to behave identically is patently false as MSVC's behavior is a superset of the behavior provided by a pointer.
> suggesting that holding this opinion is a sign of inadequacy is condescending.
If you care about writing correct code, then do not mix the very concept of pointers, arrays, references, and iterators with one another. They are all related to one another but have very important differences to the point that calling them literally identical to one another is a sign of inadequate understanding. It happens far too often and inevitably leads to poor code, bugs, and a fundamentally poor understanding of what these things represent conceptually.
Second of all, nobody is saying that pointers, arrays, references, and iterators are identical in general. They are clearly related concepts but differ in fundamental ways. However, my point was that a vector iterator needs to behave like a pointer would and can in fact be implemented as a naked pointer. Pointing to MSVC's implementation having safety checks does not change that fact, because pointing to what an implementation does on undefined behavior is clearly out of scope when discussing specified behavior. I'm not going to bring address sanitizer into this discussion to show how pointers can actually be a superset of "pointer behavior" because that would just be irrelevant. The fact remains that iterators were originally meant to act like pointers, and in many cases still do. Believing this does not mean I have a poor understanding of the language.
>Second of all, nobody is saying that pointers, arrays, references, and iterators are identical in general.
The comment I replied to said, and I quote:
"I mean, literally. The iterator of an std::vector<T> is a T*."
That is what I originally replied to before you felt it necessary to interject yourself into the conversation to police my tone.
>However, my point was that a vector iterator needs to behave like a pointer would and can in fact be implemented as a naked pointer.
No, you said it is required that they behave identically, which is false, once again, I quote you:
"a vector iterator must behave identically to a pointer"
There is no such requirement that they behave identically, and there is a demonstrable example of a compiler where they do not behave identically. Furthermore your talk about it being undefined behavior is false on the basis that in general, two pointers of type T* may compare with one another, for example the following is valid:
auto v1 = new int();
auto v2 = new int();
v1 == v2; // This is valid.
However, this is undefined behavior. auto v1 = vector_a.begin();
auto v2 = vector_b.begin();
v1 == v2; // This is undefined behavior.
Only iterators to the same vector may be compared, which is not true of pointers.>Believing this does not mean I have a poor understanding of the language.
I did not say you have a poor understanding of the language as a whole, I said if you believe that an iterator to a vector is identical to pointer as you have claimed, then you have a poor understanding of iterators.
I stand by that statement and I would suggest that you not be so sensitive about being wrong about a corner case of the language and instead simply accept this to be a fact, learn something new, and move on with your day. I learn new things about C++ all the time, sometimes from polite people, sometimes from jerks, it's all the same because what matters is the learning. Something doesn't become wrong just because the person who told it to you said it in a way you disapprove of.
Having said that, all the best to you Sir and thank you for engaging in this discussion. I have said all I think is appropriate given the topic.
Reminder that I was not the one who passed up the opportunity to reply to 'einpoklum with a simple "iterators can behave like pointers, but vector's iterators don't have to be pointers. You can see this here: https://godbolt.org/z/Y5f4d74Kb". I'm just annoyed that you went after this person for coming to an incorrect conclusion from what is clearly a correct position, which is that iterators were intended to "morally" be pointers, especially in cases where they iterate over contiguous containers. You are correct that I claimed that vector iterators must behave identically to pointers, but I really meant to say that this is only in cases where the iterator has behavior defined on it: I alluded to it previously when I mentioned that GCC and Clang wrap pointers in their own custom iterator classes to prevent people from accidentally using operations on them that are not legal. There's a bunch of other things that the iterator doesn't guarantee, like being directly casted to an integer.
But, stepping back a bit: do you really disagree with a claim that a vector iterator is meant to be a little pointer into the internal buffer it has, with a bit of additional restrictions on top that are reasonable to add? This seems to be a very strange thing to disagree with, and I would like to hear more about why you hold that position.
This being C++, being pedantic is a Good Thing.
The distinction between reference and pointer invalidation, and the requirements and axioms of the various iterator concepts are all valid things to point out, especially in a thread about C++ footguns.
You say that as if you think that makes it better.
> > You say that as if you think that makes it better.
> Absolutely!
Indeed, you absolutely did. Which sucks, because it absolutely isn't.
HTH: https://en.wikipedia.org/wiki/Tone_policing
(See also: https://en.wikipedia.org/wiki/Sealioning )
https://en.cppreference.com/w/cpp/container/vector
It _may_ be a T, or anything satisfying LegacyRandomAccessIterator.
> just like a cat is animal does not imply an animal is a cat,
It's not "just like" that. Among other differences between the pairs of concepts: The concept of an animal didn't arise as a generalization of cats.
Most of the costs are not counted as runtime for your process, so benchmarks invariably appear to show GC as costing less overhead than it does. Among the costs, as with all the myriad varieties of caching done in modern systems, is that GC makes it hard to know the costs of design choices you make. Many of the costs are in making the caches less effective.
Most languages either don't have the concept of classes or ensure that invalidating references is an explicit operation (such as calling the destructor).
But vector is usually what one should reach for unless one has a good reason not to. The only danger really comes when the lifetime of iterators and the thing they point to become a bit decoupled, even due to insertion!
If anyone is looking to write a custom iterator, I definitely recommend Boost's new stl interfaces: https://github.com/boostorg/stl_interfaces
https://www.boost.org/doc/libs/1_76_0/doc/html/boost/contain...
It's not as much as "feeling free" as it is having to reallocate the array because you added elements beyond it's capacity and they had to be stored somewhere.
So in my case the problem was very much that it took the liberty of doing something I wasn't expecting it to.
vector<int> v = {42};
...
auto x = v[0]
v.clear();
// if x was deduced as reference, using x at this point
// would be UB
std::cout<< x;Even the notable experts get things wrong when it comes to how complex and bloated C++ is.
> If the placeholder-type-specifier is of the form type-constraint auto, the deduced type T' replacing T is determined using the rules for template argument deduction.
https://eel.is/c++draft/dcl.spec.auto
Now if you program in C++ regularly and don't know that in
template<typename T>
void f(T t) { }
f(something);
does a copy of something, I don't know what to say - I have never met any professional c++ programmer not knowing the language works that way template<typename T>
void f(T) {}
f({1, 2, 3});
auto x = {1, 2, 3};
>Now if you program in C++ regularly and don't know that in...I program in C++ daily and no, I didn't know that a copy is made. What I do know, as an actual professional C++ programmer, is that whether a copy is performed is incredibly complex and depends on numerous factors such as whether a move constructor exists and "something" is an lvalue but not an rvalue reference, or if "something" is an rvalue (which is not the same as an rvalue reference) and T defines a move constructor.
And finally if something is the same type as T and T has a copy constructor, then the final question is whether copy elision will be performed, which the standard specifies is a valid optimization even if said optimization would change the observable behavior of the program.
That's what I... as a professional C++ programmer know but I fully admit that C++ is such a complex beast of a language that I am almost definitely missing a few corner cases.
Exceptions don't mean that the general rule does not apply ? I don't know how it is possible to be more explicit that "the deduced type T' replacing T is determined using the rules for template argument deduction.". That'd be like saying that "priority to the right" when driving isn't a rule because there is sometimes a "stop".
> I program in C++ daily and no, I didn't know that a copy is made.
you're kidding
> whether a copy is performed is incredibly complex and depends on numerous factors such as whether a move constructor exists and "something" is an lvalue but not an rvalue reference, or if "something" is an rvalue (which is not the same as an rvalue reference) and T defines a move constructor.
but `something` cannot be a rvalue in f(something);
maybe in f(something()); or f(some + thing); but just a variable named "something", as is, passed to a template argument or auto will necessarily lead to the creation of a new value of the same type. Whether it is copied, moved, materializes three different types because people forgot to mark their constructors explicit or whatever else frankly does not matter much in my experience (after all people survived for 30 years without move semantics) - what matters is whether a new value is created (which I personally and improperly call a "copy" no matter what - I should have been more precise but it's really the most useful distinction to make in practice imho as it means that some function will be called) or just a reference to existing value which is always a no-op no matter what.
> And finally if something is the same type as T and T has a copy constructor, then the final question is whether copy elision will be performed,
I've been at it for 15 years now and I don't remember one time in non-toy-example-code where copy elision taking place or not did matter. Thinking about is is a self-inflicted problem ; expect that a copy happens, if it's slow, profile and if it's not, be happy.