I am using emplace_back
to add items to an std::deque
. During its construction, the new item may add other items to the std::deque
it is being constructed into. This leads to very interesting behavior. Consider this snippet:
#include <iostream>
#include <deque>
#include <string>
struct foo
{
std::string m_marker;
foo(std::deque<foo>& into, const std::string& marker, bool createInner = true)
: m_marker(marker)
{
if (createInner)
{
std::cout << "Marker is " << m_marker << std::endl;
into.emplace_back(into, "my other marker", false);
std::cout << "Marker is now " << m_marker << std::endl;
}
}
};
int main()
{
std::deque<foo> foos;
foos.emplace_back(foos, "my initial marker");
std::cout << "There are " << foos.size() << " items in the deque:" << std::endl;
for (foo& f : foos)
{
std::cout << f.m_marker << std::endl;
}
}
It creates a deque
of foo
objects. The first object's marker is "my initial marker"
, and since createInner
is true
, it's going to create a second one. I would expect the following result:
Marker is my initial marker
Marker is now my initial marker
There are 2 items in the deque:
my initial marker
my other marker
However, with clang++ (tags/Apple/clang-421.11.66) and libc++ (not sure what version it is), this is what I get:
Marker is my initial marker
Marker is now my other marker
There are 2 items in the deque:
my other marker
As you can see, the m_marker
field of the first object was overwritten by the second one, and the second one to show up in the deque is now empty. So obviously there's a bug somewhere, and it has to be either that it is undefined behavior to modify a deque
during a call to emplace_back
, or that libc++ is not doing its job. Which one is it?
As Howard Hinnant answered on the bug report, the standard doesn't say anything about this case but it needs to say that it will result in undefined behavior:
In each case, because of exception safety considerations, the container is not "altered" until the construction of a foo is complete. That way, if the foo constructor throws, the container state is unchanged. For example vector.size() is not altered until the foo constructor is complete. Therefore when the second construction starts before the first one is complete, it is still appending to a zero-length vector.
This is not an issue that can be addressed in the implementation of the containers. The standard needs to say "don't do that."
So don't expect it to work with any implementation.