Can I reliably emplace_back in a vector of a type that does not have an assignment operator?

101 views Asked by At

I made some tests in GCC, Clang and MSVC and found out that emplace_back never calls an assignment operator on the contained class. It only calls the copy or move constructor when reallocation takes place. Is this behavior somehow guaranteed by the standard?

The use case is that I have some classes to be sequentially stored in a number that only grows along time until the whole vector is destructed. I'd be happy to clean my code from the assignment operators.

3

There are 3 answers

2
François Andrieux On BEST ANSWER

These requirements are documented at cppreference.com.

For std::vector<T>::emplace_back :

Type requirements

-T (the container's element type) must meet the requirements of MoveInsertable and EmplaceConstructible.

Additionally std::vector has a general requirement that the type has to be Erasable.

EmplaceConstructible requires that the type is constructible using the given allocator, with the arguments provided. Since you are using the default allocator std::allocator<T>, this just means that the type has an accessible constructor with those arguments.

MoveInsertable requires that the type is constructible using the given allocator, using an rvalue of type T. For std::allocator this means that the type has an accessible move constructor.

Erasable requires that the type is destructible using the given allocator. For std::allocator this means that is has an accessible destructor.

That's it (except for some rules about complete vs. incomplete types). There are no requirements that mention assignment operators.

But until C++11, it used to be that the type always had to be be CopyAssignable and CopyConstructible. This has since been relaxed. Now, except for Erasable, the requirements just depend on the operations performed on the vector.

2
Lorah Attkins On

Yes, emplace back will call the move constructor provided one is available (or construct in place):

#include <iostream>
#include <vector> 

struct Pt {
    Pt() = default;
    
    Pt& operator=(Pt const& other) = delete;
    
    Pt(Pt const&) { std::cout << "copy ctor" << std::endl; }
    Pt(Pt&&){ std::cout << "move ctor" << std::endl; }
};

int main(int argc, char *argv[]) 
{
    std::vector<Pt> v;
    
    v.emplace_back(Pt{});
   
    return 0;  
}  

Demo

The actual constraints applied to the type are for it to be MoveInsertable and EmplaceConstructible

0
asu On

This is what cppreference says about emplace_back:

  • T (the container's element type) must meet the requirements of MoveInsertable and EmplaceConstructible.

For EmplaceConstructible, the requirement is for std::allocator_traits<A>::construct(m, p, args); to be valid. This is the boring constraint only useful for actually emplacing elements.

MoveInsertable appears to be the concept required for reallocation to be possible.
For MoveInsertable, the requirement is for std::allocator_traits<A>::construct(m, p, rv); to be valid, where rv is a rvalue expression of type T (i.e. your type).

construct's definition is sort of confusing but for most intents and purposes, it seems to be mere parameter forwarding to the constructor.

In other words, these two concepts seem to confirm that the only constraints to emplace_back is that you need to provide:

  • the actual constructor that lets you construct in-place (duh)
  • the move constructor (or copy if missing) for relocation when reallocation happens