C++11 emplace_back on vector<struct>?

C++C++11

C++ Problem Overview


Consider the following program:

#include <string>
#include <vector>

using namespace std;

struct T
{
	int a;
	double b;
	string c;
};

vector<T> V;

int main()
{
	V.emplace_back(42, 3.14, "foo");
}

It doesn't work:

$ g++ -std=gnu++11 ./test.cpp
In file included from /usr/include/c++/4.7/x86_64-linux-gnu/bits/c++allocator.h:34:0,
                 from /usr/include/c++/4.7/bits/allocator.h:48,
                 from /usr/include/c++/4.7/string:43,
                 from ./test.cpp:1:
/usr/include/c++/4.7/ext/new_allocator.h: In instantiation of ‘void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = T; _Args = {int, double, const char (&)[4]}; _Tp = T]’:
/usr/include/c++/4.7/bits/alloc_traits.h:253:4:   required fromstatic typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type std::allocator_traits<_Alloc>::_S_construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = T; _Args = {int, double, const char (&)[4]}; _Alloc = std::allocator<T>; typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type = void]’
/usr/include/c++/4.7/bits/alloc_traits.h:390:4:   required fromstatic void std::allocator_traits<_Alloc>::construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = T; _Args = {int, double, const char (&)[4]}; _Alloc = std::allocator<T>]’
/usr/include/c++/4.7/bits/vector.tcc:97:6:   required fromvoid std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {int, double, const char (&)[4]}; _Tp = T; _Alloc = std::allocator<T>]’
./test.cpp:17:32:   required from here
/usr/include/c++/4.7/ext/new_allocator.h:110:4: error: no matching function for call toT::T(int, double, const char [4])’
/usr/include/c++/4.7/ext/new_allocator.h:110:4: note: candidates are:
./test.cpp:6:8: note: T::T()
./test.cpp:6:8: note:   candidate expects 0 arguments, 3 provided
./test.cpp:6:8: note: T::T(const T&)
./test.cpp:6:8: note:   candidate expects 1 argument, 3 provided
./test.cpp:6:8: note: T::T(T&&)
./test.cpp:6:8: note:   candidate expects 1 argument, 3 provided

What is the correct way to do this and why?

(Also tried single and double braces)

C++ Solutions


Solution 1 - C++

You need to explicitly define a ctor for the class:

#include <string>
#include <vector>

using namespace std;

struct T
{
    int a;
    double b;
    string c;

    T(int a, double b, string &&c) 
        : a(a)
        , b(b)
        , c(std::move(c)) 
    {}
};

vector<T> V;

int main()
{
    V.emplace_back(42, 3.14, "foo");
}

The point of using emplace_back is to avoid creating a temporary object, which is then copied (or moved) to the destination. While it is also possible to create a temporary object, then pass that to emplace_back, it defeats (at least most of) the purpose. What you want to do is pass individual arguments, then let emplace_back invoke the ctor with those arguments to create the object in place.

Solution 2 - C++

For anyone from the future, this behavior will be changed in C++20.

In other words, even though implementation internally will still call T(arg0, arg1, ...) it will be considered as regular T{arg0, arg1, ...} that you would expect.

Solution 3 - C++

Of course, this is not an answer, but it shows an interesting feature of tuples:

#include <string>
#include <tuple>
#include <vector>

using namespace std;

using T = tuple <
    int,
    double,
    string
>;

vector<T> V;

int main()
{
    V.emplace_back(42, 3.14, "foo");
}

Solution 4 - C++

If you do not want to (or cannot) add a constructor, specialize allocator for T (or create your own allocator).

namespace std {
    template<>
    struct allocator<T> {
        typedef T value_type;
        value_type* allocate(size_t n) { return static_cast<value_type*>(::operator new(sizeof(value_type) * n)); }
        void deallocate(value_type* p, size_t n) { return ::operator delete(static_cast<void*>(p)); }
        template<class U, class... Args>
        void construct(U* p, Args&&... args) { ::new(static_cast<void*>(p)) U{ std::forward<Args>(args)... }; }
    };
}

Note: Member function construct shown above cannot compile with clang 3.1(Sorry, I don't know why). Try next one if you will use clang 3.1 (or other reasons).

void construct(T* p, int a, double b, const string& c) { ::new(static_cast<void*>(p)) T{ a, b, c }; }

Solution 5 - C++

This seems to be covered in 23.2.1/13.

First, definitions:

> Given a container type X having an allocator_type identical to A and a > value_type identical to T and given an lvalue m of type A, a pointer p > of type T*, an expression v of type T, and an rvalue rv of type T, the > following terms are defined.

Now, what makes it emplace-constructible:

> T is EmplaceConstructible into X from args , for zero or more > arguments args, means that the following expression is well-formed: > allocator_traits::construct(m, p, args);

And finally a note about the default implementation of the construct call:

> Note: A container calls allocator_traits::construct(m, p, args) to > construct an element at p using args. The default construct in > std::allocator will call ::new((void*)p) T(args), but specialized > allocators may choose a different definition.

This pretty much tells us that for a default (and potentially the only) allocator scheme you must have defined a constructor with the proper number of arguments for the thing you're trying to emplace-construct into a container.

Solution 6 - C++

you have to define a constructor for your type T because it contains an std::string which is not trivial.

moreover, it would be better to define (possible defaulted) move ctor/assign (because you have a movable std::string as member) -- this would help to move your T much more efficient...

or, just use T{...} to call overloaded emplace_back() as recommended in neighboug response... everything depends on your typical use cases...

Solution 7 - C++

You can create the struct T instance and then move it to the vector:

V.push_back(std::move(T {42, 3.14, "foo"}));

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAndrew TomazosView Question on Stackoverflow
Solution 1 - C++Jerry CoffinView Answer on Stackoverflow
Solution 2 - C++Red XIIIView Answer on Stackoverflow
Solution 3 - C++riciView Answer on Stackoverflow
Solution 4 - C++Mitsuru KariyaView Answer on Stackoverflow
Solution 5 - C++Mark BView Answer on Stackoverflow
Solution 6 - C++zaufiView Answer on Stackoverflow
Solution 7 - C++AlexBView Answer on Stackoverflow