1

I was using emplace_back on a vector of my class A that has a const reference member variable of class Handle with some success. However I needed to change it to use emplace instead to be able to insert at certain index. But that doesn't seem to compile. Looking at cppreference, I can't tell what the difference between the two methods is, except that one takes an explicit iterator

#include <vector>

class Handle
{
    int do_something();
};

class A
{
public:
    A(int a, float d, const Handle& h) : a_(a), d_(d), h_(h) {}
private:
    int a_;
    float d_;
    const Handle& h_;
};

int main()
{
    Handle h;
    std::vector<A> foo;
    foo.reserve(10);
    foo.emplace_back(3, 4.5, h); // OK
    // foo.emplace(foo.begin() + 3, 3, 4.5, h); // NOT OK
}

I get an error on using emplace :

In file included from main.cpp:7:
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/vector:1759:18: error: object of type 'A' cannot be assigned because its copy assignment operator is implicitly deleted
            *__p = _VSTD::move(__tmp.get());
                 ^
main.cpp:41:6: note: in instantiation of function template specialization 'std::vector<A>::emplace<int, double, Handle &>' requested here
        foo.emplace(foo.begin() + 3, 3, 4.5, h);
            ^
main.cpp:22:16: note: copy assignment operator of 'A' is implicitly deleted because field 'h_' is of reference type 'const Handle &'
        const Handle& h_;

Do I need to change my class design to accommodate using emplace?

I found this question but it doesn't seem to provide an alternative

rookie
  • 1,168
  • 3
  • 14
  • 25
  • You can emplace to avoid an unwanted assignment, but `vector` requires everything in it be copyable or movable because it does a lot of copying and moving. For example say in the future you insert an item into the vector anywhere but at the back. All of the items that will now be after the inserted item mist shuffle over one slot. And what happens when the vector is resized? All of the items in it need to be copied to new storage. – user4581301 Jul 14 '23 at 01:31
  • Copying an item with constant values in it is problematic. You can't change the constant value in the item you're copying into, so you can't really finish off the copy. If the value is always the same for all instances, consider making the member `static` so that it doesn't need to be copied. If not, consider making the variable `private` and never allowing anyone other than the copying special member functions to change it. – user4581301 Jul 14 '23 at 01:34
  • So I didn't understand why this showed up only when using emplace and not emplace_back. From the compile error, I understand that the copy assignment operator is deleted because of the const reference member variable. But if a vector resize would need to copy stuff over, why does the error not show up in `emplace_back` ? Am I silently doing something wrong? – rookie Jul 14 '23 at 01:35
  • 1
    `emplace` allows you to put an item in the middle of the `vector`, forcing every item already in the `vector` that would be after the new item to move. And they can't move. You aren't tripping over the item you are emplacing, you're tripping over the items that are potentially already there. – user4581301 Jul 14 '23 at 01:36
  • 1
    I blew it a few comments up. When you resize you're creating new items that are copies of the originals. Since the new item doesn't exist yet, you're using the copy constructor rather than assigning. You can't assign a constant, but you can initialize one. – user4581301 Jul 14 '23 at 01:39
  • Ah, understood. Thank you for the explanation , the error makes sense to me now. So it looks like I'm stuck with using `emplace_back` or `push_back` due to the const reference member variable constraint – rookie Jul 14 '23 at 01:45
  • 2
    FWIW `foo.emplace(foo.begin() + 3, 3, 4.5, h);` has undefined behavior because `foo.begin() + 3` is past the past the end iterator. – NathanOliver Jul 14 '23 at 02:34
  • You can implement copy assignment in classes with consts and references which will allow your code to work. At least as of c++20. See [this](https://stackoverflow.com/a/72975020/5282154) Probaly something to avoid absent compelling reasons to do it. – doug Jul 14 '23 at 02:56
  • @NathanOliver-IsonStrike You are right. I should have just chosen `foo.begin() + i` where i starts from 0. That would have been more representative of my use-case. – rookie Jul 14 '23 at 03:04
  • @doug Thanks, I'll look through the C++20 example, but for now latest I could do is C++17. – rookie Jul 14 '23 at 03:05
  • Not possible with c++17. Requires c++20, and c++23.. There has long been very good advice to avoid const and reference members in classes because they impose significant restraints due to their immutable requirements. No workaround w/o UB. c++20 changed that. – doug Jul 14 '23 at 03:15
  • I actually don't see any issue for this particular use case, even in c++17. There is no reason for attempting instantiation of the now deleted assignment operator as no elements exist and need to be shifted with the insertion of a new element. Only storage allocated for them. Their lifetime hasn't started. Looks like a technical defect in the library code. That said, inserting an element that results in gaps where elements don't exist (lifetime started) is twitchy. – doug Jul 14 '23 at 03:27
  • 1
    Vector elements must be move-assignable for `emplace` to work. Yours are not. `emplace_back` by contrast does not require them to be move-assignable. – n. m. could be an AI Jul 15 '23 at 09:41

1 Answers1

1

Your code is attempting to insert an element into a non-end position in an empty std::vector, which is not allowed. The std::vector::emplace method inserts an element at the specified position, but that position must be within the range of the container (i.e., between begin() and end()).

In your code, the position foo.begin() + 3 is out of range for the current foo. You can modify your code in the following ways: Use the emplace_back method instead of the emplace method, which will append the new element to the end of the std::vector.

foo.emplace_back(3, 4.5, h);

Or, increase the size of the std::vector using the resize method before using the emplace method.

foo.resize(10);
foo.emplace(foo.begin() + 3, 3, 4.5, h);

However, beware that the above code will create A objects with default construction at the first three positions of foo. If the A class does not have a default constructor, or if you do not want to create these default objects, you might need to seek other solutions.

Moreover, the A class contains a constant reference to the Handle class, which causes the copy constructor and copy assignment operator of the A class to be deleted. This is because a reference member cannot change the object it refers to once it has been initialized. If std::vector needs to move or copy elements internally (for example, when the container needs to adjust its internal storage size), it will need to use these deleted copy constructors or copy assignment operators, leading to a compile error. You may need to modify the design of the A class to make it copyable or movable.

yxyc-137
  • 37
  • 2
  • `foo.resize(10)` s/b `foo.reserve(10)` because resize will attempt to default construct all the expanded vector's objects. – doug Aug 03 '23 at 02:48