This is with Visual Studio 2012.
static void func(
...,
const std::vector<std::string> &opt_extra_args_strs,
...)
{
// THIS ASSERTS: "vector iterators incompatible"
for (const std::string &arg_str : opt_extra_args_strs) {
... body does not modify opt_extra_args_strs
// BUT THIS WORKS:
for (size_t a_ix = 0; a_ix < opt_extra_args_strs.size(); a_ix++) {
const std::string &arg_str = opt_extra_args_strs[a_ix];
}
I am not modifying the vector at all in the loop body, and in fact, the assertion happens before the first iteration. The vector looks right in the debugger, but I don't know enough about STL to look for corruption. Inside the STL the assertion failure comes from:
void _Compat(const _Myiter& _Right) const {
// test for compatible iterator pair
if (this->_Getcont() == 0 // THIS FAILS (_Getcont() == 0)
...) {
_DEBUG_ERROR("vector iterators incompatible");
with this->_Getcont()
being NULL because (_Myproxy
is NULL in the _Iterator_base12
).
The call stack is:
msvcp110d.dll!std::_Debug_message(const wchar_t * message, const wchar_t * file, unsigned int line) Line 15 C++
Main.exe!std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > >::_Compat(const std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > > & _Right)
Main.exe!std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > >::operator==(const std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > > & _Right)
Main.exe!std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > >::operator!=(const std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > > & _Right)
Main.exe!run_test(..., const std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > > & opt_extra_args_strs)
...
I suspect the code setting up the vector is screwing it up somehow, but I am not sure. I am having difficulty writing a simpler reproducer as well, but the program should be totally deterministic (single threaded, not random variables, always asserts).
Additionally, I also run into a different similar assertion failure earlier "vector iterator + offset out of range"
with the snippet (on the same vector)
template <typename T>
class Elsewhere {
virtual void earlier(
....
std::vector<T> &v) const
{
v.emplace_back(); // empty construction of a T
// T &t = v.back(); // assertion failure
T &val = to[to.size() - 1]; // but this works
... mutates val.
With T = std::string
(in fact, the same vector).
I mention this because within STL the condition for this failure ends up also being this->_Getcont() == 0
, and I suspect them to be related. What does it mean for _Getcont()
to be 0 in a Vector_const_iterator
?
The vector comes out of a container
template <typename T>
struct type {
T m_value;
operator const T &() const {
return value();
}
const T &value() const {
return m_value;
}
};
type<std::vector<std::string>> &t = ... method call that returns ref to it;
... t gets set
func(t); // implicit conversion to (const std::vector<std::string> &)
I finally found the problem. A path deep in the vector's setup code was clobbering the vector's state (memset'ing it to 0's as part of a larger chunk of memory). This caused no harm for the first three fields in vector: _Myfirst
, _Mylast
, and _Myend
since those fields are 0 in an initial vector. Moreover, most things such as the array index operator and other methods such as push_back still functioned correctly. However, the fourth field _Myproxy
was initially non-zero, and clearing it disabled iterator-based functionality. Hence, for-each loop, vector::back()
, and others all fail with different false errors such as false bounds checks, false incompatible iterators, etc...
Regarding the first item with the range for loop, unless there is something new in the C++11 standard, the incompatible iterator message is 100 percent accurate. That line is attempting to assign a constant string reference to an iterator from the vector. An iterator type is not compatible with the data type of the element being stored.
Please take a look at http://www.stroustrup.com/C++11FAQ.html#for. In that example, the auto datatype is used so that the under the hood setting of the iterator through the colon usage is set at the beginning of the container (e.g. much less keyboard typing than the old days). Then more under the hood things occur that make sure the iterator never passes the last element. The equivalent of the range for loop above (as written) is:
// added a local string to clearly indicate types
std::string s1;
const std::string &arg_str = s1;
const std::vector<std::string> :: iterator i;
i = opt_extra_args_strs.begin(); // happens inside the range for
for (arg_str = i; i < opt_extra_args_strs.end(); i++)
{
// loop body
}
As soon as the arg_str reference is reassigned to the starting point for the iterator, the compiler should complain and print an error.
The second for loop is an older alternative to avoid iterators, but continue to use the other methods available for range checking a dynamic container like a vector, and stay within the container bounds for the quantity of elements currently inside it. That loop has to always work, since the loop body is assigning a locally allocated const string reference to each element (also a string, but not a constant string) within the container. An iterator type is never attempted to be assigned to the string reference inside the second for loop body.
This new range for has many nice features to reduce keyboard typing as much as possible. However, it is probably best to use the auto keyword and subsequent datatype assignments that the compiler will always get right (assuming it remains in C++11 compliance forever).
The assertion failure of
T &t = v.back(); // assertion failure
is also completely correct for an empty vector (Start Edit).
The back() method is a recent addition to the STL for me. I am sorry. In my haste to complete the original post before heading out, I read the word back() and translated that in my brain to end().
If the end() method is called: The T &t is not the same type as std :: vector<T> :: iterator
, which is what is returned when v.end() is called. In terms of types it looks more like this:
T &t = std::vector<T> :: iterator
After reviewing the details on std::vector::back(), if back() is called on an empty vector, the behavior is undefined. More than likely, finding the flaw would be only at run time. For reference, try this: http://www.cpluscplus.com/reference/vector/vector/back. For always calling back(), the first thing that has to be confirmed is that at least one element exists inside the vector. Prior to that, since emplace_back(Args&&... args);
is the prototype at http://www.cpluscplus.com/reference/vector/vector/emplace_back, calling it with no parameter to insert after the current last element is not defined. The reference page says, "If allocator_traits::construct is not supported with the appropriate arguments, it causes undefined behavior." The code will probably have to become something more like:
//
// start of the 'earlier' function body
//
std::string s;
v.emplace_back(s); // add one element to v
//
// obtain a reference to the last element (should be a copy of s above)
//
T &t = v.back();
//
// It is not clear what the vector 'to' is and how it exists inside 'earlier'
// as long as 'to' has at least one element, then the code below will
// set the local reference variable to the last element of 'to'.
// If not, then another run time error is likely with attempting to access to[-1]
// and then attempting to assign the non-existent element to T& val
//
T &val = to[to.size() - 1];
I hope this helps understand the difference between an iterator, adding an element after the current last element, and the data element type stored inside a container.
(End Edit)
It would be very surprising that the memory assignment area is a problem. Visual Studio 2012 would have a host of unhappy C++ engineers. It is highly recommended to undo whatever modifications occurred inside the stl source code.
I encountered this assert failure myself. I discovered the cause to be that something I was calling within the loop was modifying the vector that I was iterating through.