I have a list of sets:
std::list<std::set<int>> nn = {{1,2},{4,5,6}};
and I want to print out the element which end() refers to:
for (auto el : nn){
std::cout << *el.end() << std::endl;
}
What I get as a result is: 2 and 3.
I do not know where do these values come from. Can someone help me plz?
CodePudding user response:
It is not allowed to de-reference the end()
iterator. Doing so causes undefined behavior. It doesn't refer to any element of the container, but one past the last element.
The reason that end()
"points" after the last element, is that it is necessary to distinguish empty containers. If end()
was referring to the last element and begin()
to the first, then if begin() == end()
that would mean that there is one element in the container and we can't distinguish the case of an empty container.
For containers that support it, to access the last element of the container you can use .back()
, which will return a reference, not an iterator. But this is only allowed if there is a last element, i.e. if the container is not empty. Otherwise you have again undefined behavior. So check .empty()
first if necessary.
std::set
does not have the back()
member and is not really intended to be used this way, but if you really want to access the last element, which is not the last element in the constructor initializer list, but the last element in the <
order of the elements, then you can use std::prev(el.end())
or el.rbegin()
("reverse begin") which will give you an iterator to the last element. Again, dereferencing this iterator is only allowed if the container is not empty. (For std::prev
forming the iterator itself isn't even allowed if the container is empty.)
Undefined behavior means that you will have no guarantees on the program behavior. It could output something in one run and something else in another. It could also output nothing, etc.
There is no requirement that you will get the output you see. For example, current x86_64 Clang with libc as standard library implementation, compiles (with or without optimization) a program that prints 0
twice. https://godbolt.org/z/nWMss1fqe
Practically speaking, assuming the compiler didn't take advantage of the undefined behavior for an optimization that drastically changes the program from the "intended" program flow, you will likely, depending on the implementation of the standard library, read some internal memory of the std::set
implementation in the standard library, get a segmentation fault if the indirection points to inaccessible memory or incidentally (with no guarantees) refer to other values in the container.
CodePudding user response:
Question 1
What does end() refere to
Answer
end()
is a public member function of std::set
that returns an iterator to the past-the-end element in the set
container.
Question 2
I do not know where do these values come from.
Answer
When you wrote:
std::cout << *el.end() << std::endl;//this is undefined behavior
In the above statement you are dereferencing the iterator that was returned by the end()
member function.
But note that if we dereference the iterator that was returned by this member function then we get undefined behavior.
Undefined behavior means anything1 can happen including but not limited to the program giving your expected output. But never rely(or make conclusions based) on the output of a program that has undefined behavior.
1For a more technically accurate definition of undefined behavior see this where it is mentioned that: there are no restrictions on the behavior of the program.