Search code examples
c++gccvectormoveemplace

Does gcc have a extension overload for std::vector::emplace_back?


According to cppreference, std::vector::emplace_back has only one signature, which is:

template< class... Args >
reference emplace_back( Args&&... args );

And in the description, it says emplace_back is supposed to forward each of its arguments to the constructor of the type in the vector. However, when I tested the following code using gcc 12.2, it successfully compiles:

#include <iostream>
#include <vector>

class Foo
{
public:
    int x;
    int y;
    Foo(int x, int y) : x(x), y(y)
    {}
};

int main()
{
    std::vector<Foo> foos;
    Foo foo(1, 2);
    foos.push_back(std::move(foo));
    foos.emplace_back(foo); // weird
}

(See on compiler explorer)

I expected the line foos.emplace_back(foo); to fail the compilation. It seems as if emplace_back has this overload:

template< class T >
reference emplace_back( T& arg );

which isn't mentioned in the documentation. Am I missing something or is this just a compiler extension?


Solution

  • foos.emplace_back(foo); // weird
    

    That's weird indeed! You ask to emplace_back, meaning you're calling the constructor, and you're passing foo to the constructor. Hence, you're calling the copy constructor. push_back would have done the same!

    That's an implicitly defined default copy constructor, see cppreference on Copy Constructors:

    If no user-defined copy constructors are provided for a class type (struct, class, or union), the compiler will always declare a copy constructor as a non-explicit inline public member of its class.

    You didn't define a copy constructor, so the compiler did that for you, as dictated by C++. You can avoid that and make your compilation fail:

    class Foo
    {
    public:
        int x;
        int y;
        Foo(int x, int y) : x(x), y(y)
        {}
        
        //! Explicitly deleted default copy constructor.
        Foo(const Foo&) = delete;
    };
    
    foos.push_back(std::move(foo));
    

    He're you're calling the move constructor. Note that you're using foo after moving from it, that's more or less illegal (you throwing std::move at foo means that foo is no longer supposed to hold any resources.)

    Should you really happen to just want to delete the move constructor, same syntax, basically: Foo(Foo&&) = delete;.