Question

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?

Was it helpful?

Solution

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top