10

I want to use vector::emplace to default construct a non-copyable and non-assignable object and then use specific methods on the object using an iterator to the newly created object. Note that there are no parameterized constructors of the class just the default constructor. A simple example is:

#include <iostream>
#include <vector>
using namespace std;

class Test {
public:
    Test() {}
private:
    Test(const Test&) = delete;             // To make clas un-copyable.
    Test& operator=(const Test&) = delete;

    int a_;
};

int main() {
    vector<Test> test_vec;
    test_vec.emplace_back();     // <---- fails

    return 0;
}

vector::emplace() constructs a new object but requires arguments to a non-default constructor. vector::emplace_back() will construct at the end of the vector.

Is there a way to emplace with default construction. Is there a way to use piecewise construction or default forwarding perhaps using std::piecewise_construct as there is for maps? For example, in the case of maps, we can use:

std::map<int,Test> obj_map;
int val = 10;
obj_map.emplace(std::piecewise_construct,
                std::forward_as_tuple(val),
                std::forward_as_tuple());

Is there something similar for vectors?

Chizzy C
  • 181
  • 1
  • 1
  • 8
  • Is your type movable? – dyp Nov 21 '14 at 18:45
  • Yes, but it is not assignable or copyable. Please see an example [here](http://ideone.com/49LKaP). Using `emplace_back()` results in a compile error. – Chizzy C Nov 21 '14 at 19:05
  • @RizwanC That error is because the type is not *movable*. The user-declared copy constructor suppresses generation of the default move constructor. You need to declare a defaulted move constructor (and move-assignment if you like), at which point you don't need the deleted copy operations, since declaring the move operations will suppress implicit generation of the copies. – Casey Nov 21 '14 at 19:06
  • That type is NOT movable. The move-functions are not implicitly declared (or deleted) since you have explicitly declared the copy-functions. – dyp Nov 21 '14 at 19:06
  • I see, so is it safe to assume that there is no way we can emplace with default constructors into a vector with this type? – Chizzy C Nov 21 '14 at 19:09
  • You cannot emplace *at all* with this type. Emplacing can require allocating a new buffer and moving the old values to that new buffer (run-time decision). This won't work with your type. – dyp Nov 21 '14 at 19:12
  • Thanks for that information. Could you also clarify why this is different in the case of maps where you can still use emplace with piecewise construct a non-movable type? – Chizzy C Nov 21 '14 at 19:14
  • Because a map allocates each node individually; it does not need to move their values around (like a linked list). If you add an element, a new node is allocated and linked to the existing map. The values stored in the existing map do not need to be copied/moved. – dyp Nov 21 '14 at 19:15

3 Answers3

11

vector::emplace_back() will construct at the end of the vector but also require arguments.

Parameter packs can be empty. Thus the variadic template emplace_back can be called without arguments; I.e.

vector<VeryLimitedClass> vec;
vec.emplace_back();

Is valid code that initializes an object of type VeryLimitedClass through its default constructor and "emplaces" it at the back of vec.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • @dyp ... you know exactly what I meant. I don't want to confuse the OP with standard terminology. :) – Columbo Nov 21 '14 at 18:47
  • Interestingly, there's no guarantee what kind of initialization is happening neither from the vector nor from the allocator requirements. But yes, it probably doesn't care much. -- I haven't upvoted this answer since I'm not sure if we already know the whole picture. If the OP has a noncopyable AND nonmovable type, `emplace` cannot be used. – dyp Nov 21 '14 at 18:48
  • 1
    @dyp Are you sure about the first part of your last comment? – Columbo Nov 21 '14 at 18:50
  • 1
    I know that I wanted to report a defect since more than a year IIRC relating to that (and vector's exception safety guarantees). I have looked quite thoroughly into this issue at that time. – dyp Nov 21 '14 at 18:51
  • E.g. if we look at the allocator requirements, it only guarantees that an object of the specified type is created; it does not say anything about the arguments or the form of initialization. -- Preliminary version of the allocator construction problem: https://groups.google.com/a/isocpp.org/d/topic/std-discussion/0yxikZInp-E/discussion – dyp Nov 21 '14 at 18:56
  • 4
    We can say with certainty that the default allocator will perform value-initialization per [allocator.members]/12, which says the effect of `construct(U* p, Args&&... args)` is equivalent to `::new((void *)p) U(std::forward(args)...)`. But we can't say with certainty that the default allocator is in use without seeing the entire translation unit, even knowing the type of the vector is `std::vector`, since `std::allocator` may be a user-defined specialization. – Casey Nov 21 '14 at 19:02
8

As pointed out by @dyp and @Casey in the comments, std::emplace will not work for vectors of the Test class as the class is neither movable nor copyable because "the user-declared copy constructor suppresses generation of the default move constructor" (@Casey).

To use emplace here, the class will need to be movable or copyable. We can make the class movable by explicitly defining (and defaulting) the move constructors:

public:
    Test(Test&& other) = default;
    Test& operator=(Test&& other) = default;

This will also implicitly make the class not-copyable "since declaring the move operations will suppress implicit generation of the copies." (@Casey)

Now we can use std::emplace_back() and then use vector::back() to call methods of the newly created object.

John
  • 2,963
  • 11
  • 33
Chizzy C
  • 181
  • 1
  • 1
  • 8
1

For map, easy:

std::map<int, Object> obj_map;
obj_map[10]; // default-constructs an object with key 10

Otherwise, what you have works too:

obj_map.emplace(std::piecewise_construct,
                std::forward_as_tuple(10),
                std::forward_as_tuple(args, to, construct, with));

[edit] The equivalent for vector is emplace_back:

obj_vector.emplace_back(); // default construct
obj_vector.emplace_back(args, to, std::move(construct), with); // forward these
Barry
  • 286,269
  • 29
  • 621
  • 977
  • 1
    It is clear that you can use `std::piecewise_construct` for maps. However, my question is about doing the same for vectors. I guess I also left some information out of the question (which I have now edited): Note that the type is not copyable or assignable so using emplace with another object is not suitable. – Chizzy C Nov 21 '14 at 18:42
  • @RizwanC Ah, I didn't understand your question. Updated it for `vector`. – Barry Nov 21 '14 at 18:49