Initialise const std::vector Class Members Efficiently - c++

Lets assume I am trying to create "immutable" class objects (i.e. members variables are defined with const). So when calling the constructor I currently call separate init functions to initialise the class members. But as a result there seems to be a lot of new vector-creating & vector-copying going on.
If the members weren't const I could perform the initialisations in the { } section of the constructor and write directly into values (which I assume would be more efficient). But this is not possible.
Are there better/cleaner/more efficient ways to design the construction of immutable classes?
#include <vector>
class Data
{
public:
const std::vector<int> values;
Data(unsigned int size, int other) : values(init(size, other)) { }
Data(const std::vector<int>& other) : values(init(other)) { }
private:
std::vector<int> init(unsigned int size, int other) {
std::vector<int> myVector(size);
for (unsigned int i = 0; i < size; ++i)
myVector[i] = other * i;
return myVector;
}
std::vector<int> init(const std::vector<int>& other) {
std::vector<int> myVector(other);
for (unsigned int i = 0; i < other.size(); ++i)
myVector[i] *= myVector[i] - 1;
return myVector;
}
};
int main() {
Data myData1(5, 3); // gives {0, 3, 6, 9, 12}
Data myData2({2, 5, 9}); // gives {2, 20, 72}
return 0;
}

Your current design is perfectly fine. The initialization takes place in the constructor's member initialization list, so it will trigger a move at worst (which is quite cheap for a vector anyway) and NRVO at best.
NRVO is Named Return Value Optimization. When a function returns a named variable with automatic storage duration, the compiler is allowed to elide the copy/move. Note that however, copy/move constructors still need to be available even in the case of an elision. Here is a dummy example to sum up that concept:
SomeType foo() { // Return by value, no ref
SomeType some_var = ...; // some_var is a named variable
// with automatic storage duration
do_stuff_with(var);
return some_var; // NRVO can happen
}
(Your init function follows that pattern.)
In C++17 you could even benefit from a guaranteed copy elision in that scenario depending on the shape of your init function. You can find out more about this in this other SO answer.
Note: Since you tagged your question c++11 I assume move semantics are available.

You say
there seems to be a lot of new vector-creating & vector-copying going on.
But I am unsure of it. I would instead expect one full creation and one move here:
init builds and returns a temporary vector (ok full vector creation, directly with the final size) which is used to initialize the const member (ok a move should occur here). We should control the generated assembly here, but a decent compiler should build the data block once, and move it into the data member.
So unless you can prove by profiling (or by looking at the assembly generated by your compiler) that things really need to be optimized here, I would gladly keep on with this code because it clearly declares the member constness.

The solution here is to remove const from the member vector so that you can perform your initialisation in place, rather than by copying.
If you want values to be readable but not writable by users of the class, you can expose a const reference to it:
class Data {
std::vector<int> values_;
// constructors...
public:
std::vector<int> const& values() const { return values_; }
};

Related

Do const and reference in function parameters cause unnecessary casts? [duplicate]

I have some pre-C++11 code in which I use const references to pass large parameters like vector's a lot. An example is as follows:
int hd(const vector<int>& a) {
return a[0];
}
I heard that with new C++11 features, you can pass the vector by value as follows without performance hits.
int hd(vector<int> a) {
return a[0];
}
For example, this answer says
C++11's move semantics make passing and returning by value much more attractive even for complex objects.
Is it true that the above two options are the same performance-wise?
If so, when is using const reference as in option 1 better than option 2? (i.e. why do we still need to use const references in C++11).
One reason I ask is that const references complicate deduction of template parameters, and it would be a lot easier to use pass-by-value only, if it is the same with const reference performance-wise.
The general rule of thumb for passing by value is when you would end up making a copy anyway. That is to say that rather than doing this:
void f(const std::vector<int>& x) {
std::vector<int> y(x);
// stuff
}
where you first pass a const-ref and then copy it, you should do this instead:
void f(std::vector<int> x) {
// work with x instead
}
This has been partially true in C++03, and has become more useful with move semantics, as the copy may be replaced by a move in the pass-by-val case when the function is called with an rvalue.
Otherwise, when all you want to do is read the data, passing by const reference is still the preferred, efficient way.
There is a big difference. You will get a copy of a vector's internal array unless it was about to die.
int hd(vector<int> a) {
//...
}
hd(func_returning_vector()); // internal array is "stolen" (move constructor is called)
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8};
hd(v); // internal array is copied (copy constructor is called)
C++11 and the introduction of rvalue references changed the rules about returning objects like vectors - now you can do that (without worrying about a guaranteed copy). No basic rules about taking them as argument changed, though - you should still take them by const reference unless you actually need a real copy - take by value then.
C++11's move semantics make passing and returning by value much more attractive even for complex objects.
The sample you give, however, is a sample of pass by value
int hd(vector<int> a) {
So C++11 has no impact on this.
Even if you had correctly declared 'hd' to take an rvalue
int hd(vector<int>&& a) {
it may be cheaper than pass-by-value but performing a successful move (as opposed to a simple std::move which may have no effect at all) may be more expensive than a simple pass-by-reference. A new vector<int> must be constructed and it must take ownership of the contents of a. We don't have the old overhead of having to allocate a new array of elements and copy the values over, but we still need to transfer the data fields of vector.
More importantly, in the case of a successful move, a would be destroyed in this process:
std::vector<int> x;
x.push(1);
int n = hd(std::move(x));
std::cout << x.size() << '\n'; // not what it used to be
Consider the following full example:
struct Str {
char* m_ptr;
Str() : m_ptr(nullptr) {}
Str(const char* ptr) : m_ptr(strdup(ptr)) {}
Str(const Str& rhs) : m_ptr(strdup(rhs.m_ptr)) {}
Str(Str&& rhs) {
if (&rhs != this) {
m_ptr = rhs.m_ptr;
rhs.m_ptr = nullptr;
}
}
~Str() {
if (m_ptr) {
printf("dtor: freeing %p\n", m_ptr)
free(m_ptr);
m_ptr = nullptr;
}
}
};
void hd(Str&& str) {
printf("str.m_ptr = %p\n", str.m_ptr);
}
int main() {
Str a("hello world"); // duplicates 'hello world'.
Str b(a); // creates another copy
hd(std::move(b)); // transfers authority for b to function hd.
//hd(b); // compile error
printf("after hd, b.m_ptr = %p\n", b.m_ptr); // it's been moved.
}
As a general rule:
Pass by value for trivial objects,
Pass by value if the destination needs a mutable copy,
Pass by value if you always need to make a copy,
Pass by const reference for non-trivial objects where the viewer only needs to see the content/state but doesn't need it to be modifiable,
Move when the destination needs a mutable copy of a temporary/constructed value (e.g. std::move(std::string("a") + std::string("b"))).
Move when you require locality of the object state but want to retain existing values/data and release the current holder.
Remember that if you are not passing in an r-value, then passing by value would result in a full blown copy. So generally speaking, passing by value could lead to a performance hit.
Your example is flawed. C++11 does not give you a move with the code that you have, and a copy would be made.
However, you can get a move by declaring the function to take an rvalue reference, and then passing one:
int hd(vector<int>&& a) {
return a[0];
}
// ...
std::vector<int> a = ...
int x = hd(std::move(a));
That's assuming that you won't be using the variable a in your function again except to destroy it or to assign to it a new value. Here, std::move casts the value to an rvalue reference, allowing the move.
Const references allow temporaries to be silently created. You can pass in something that is appropriate for an implicit constructor, and a temporary will be created. The classic example is a char array being converted to const std::string& but with std::vector, a std::initializer_list can be converted.
So:
int hd(const std::vector<int>&); // Declaration of const reference function
int x = hd({1,2,3,4});
And of course, you can move the temporary in as well:
int hd(std::vector<int>&&); // Declaration of rvalue reference function
int x = hd({1,2,3,4});

Prevent unnecessary copying between large structs

I have huge structs DataFrom and Data (which have different members in reality). Data is Created from DataFrom.
struct DataFrom{
int a = 1;
int b = 2;
};
static DataFrom dataFrom;
struct Data{
int a;
int b;
};
class DataHandler{
public:
static Data getData(const DataFrom& data2){
Data data;
setA(data, data2);
setB(data, data2);
return data;
}
private:
static void setA(Data& dest, const DataFrom& source){
dest.a = source.a;
}
static void setB(Data& dest, const DataFrom& source){
dest.b = source.b;
}
};
int main(){
auto data = DataHandler2::getData(dataFrom); // copy of whole Data structure
// ...
return 0;
}
As Data is huge, in getData function, there is a copying of whole Data structure. Can this be prevented somehow in elegant way?
I had an idea about:
static void getData( Data& data, const DataFrom& data2);
But I would prefer to retrieve data as a return value, not an output parameter.
There are two potential "copy hazards" to address here:
Copy hazard 1: The construction outside getData()
On the first line of main(), where you commented "copy of whole Data structure" - as commenters noted, the structure won't actually be copied, due to the Named Return Value Optimization, or NRVO for short. You can read about it in this nice blog post from a few years back:
Fluent{C++}: Return value optimizations
In a nutshell: The compiler arranges it so that data inside the getData function, when it is called from main(), is actually an alias of data in main.
Copy hazard 2: data and data2
The second "copy scare" is with setA() and setB(). Here you must be more pro-active, since you do have two live, valid structs in the same function - data and data2 within getData(). Indeed, if Data and DataFrom are simply large structs - then you will be doing a lot of copying from data2 to data, the way you wrote your code.
Move semantics to the rescue
If, however, your DataFrom holds a reference to some allocated storage, say, std::vector<int> a instead of int[10000] a - you could move from your DataFrom instead of copying from it - by having getData() with the signature static Data getData(DataFrom&& data2). Read more about moving here:
What is move semantics?
In my example, this would mean you would now use the raw buffer of data2.a for your data - without copying the contents of that buffer anywhere else. But that would mean you can no longer use data2 afterwards, since its a field has been cannibalized, moved from.
... or just be "lazy".
Instead of a move-based approach, you might try something else. Suppose you defined something like this:
class Data {
protected:
DataFrom& source_;
public:
int& a() { return source_.a; }
int& b() { return source_.b; }
public:
Data(DataFrom& source) : source_(source) { }
Data(Data& other) : source_(other.source) { }
// copy constructor?
// assignment operators?
};
Now Data is not a simple struct; it is more of a facade for a DataFrom (and perhaps some other fields and methods). That's a bit less convenient, but the benefit is that you now create a Data with merely a reference to a DataFrom and no copying of anything else. On access, you may need to dereference a pointer.
Other notes:
Your DataHandler is defined as a class, but it looks like it serves as just a namespace. You never instantiate "data handlers". Consider reading:
Why and how should I use namespaces in C++?
My suggestions do not involve any C++17. Move semantics were introduced in C++11, and if you choose the "lazy" approach - that would work even in C++98.
Since you've tagged this with c++17, you can write your code in a way that prevents any copying from taking place (or being able to take place), and if it compiles you will know that statically no copies will be made.
C++17 guarantees copy elision when returned from functions, which ensures no copies take place in certain circumstances. You can ensure this by changing your code so that Data has an = deleted copy-constructor, and changing getData to return a constructed object. If the code compiles correctly at all, you will be sure that no copy occurred (since copying would trigger a compile-error)
#include <iostream>
struct DataFrom{
int a = 1;
int b = 2;
};
static DataFrom dataFrom;
struct Data{
Data() = default;
Data(const Data&) = delete; // No copy
int a;
int b;
};
class DataHandler{
public:
static Data getData(const DataFrom& data2){
// construct it during return
return Data{data2.a, data2.b};
}
private:
static void setA(Data& dest, const DataFrom& source){
dest.a = source.a;
}
static void setB(Data& dest, const DataFrom& source){
dest.b = source.b;
}
};
int main(){
auto data = DataHandler::getData(dataFrom); // copy of whole Data structure
return 0;
}
This will compile without any extra copies -- you can see it here on compiler explorer

Why c++ vector inner elements/array are copied when passed by value

Why does the inner elements of the vector are copied when the vector is passed by value?
#include<vector>
using namespace std;
// this func won't modify v[2].
// Meaning v[2] (and the whole inner array) was copied
// when v is passed to the func?
void modify(vector<int> v) {
v[2] = 100;
}
// this func modify v[2]
void modify(vector<int>& v) {
v[2] = 100;
}
int main() {
vector<int> v = {1,2,3,4,5,6};
// still same
modify(v);
// modified
modified2(v);
}
I find that it's strange that the actual content of the vector is copied when the vector is passed by value. I picture that the std::vector implementation must have a pointer field which maps to an address on heap, where the actual array is located. So when the vector is passed, even by value, the address should stay the same, pointing to the same content. Something like this:
#include<iostream>
using namespace std;
// a dummy wrapper of an array
// trying to mock vector<int>
class vector_int {
public:
int* inner_array; // the actual array
vector_int(int *a) {
inner_array = a;
}
int* at(int pos) {
return inner_array+pos;
}
};
// this passes the "mocked vector" by value
// but 'inner_array' is not copied
void modify(vector_int v) {
*(v.at(2)) = 10;
}
int main() {
int* a = new int[3] {1,2,3};
vector_int v = vector_int(a);
modify(v); // v[2] is modified
}
Is this assumption about the std::vector implementation correct? What makes the vector content being copied when passed by value?
EDIT
Thanks to alter igel's answer and UnholySheep's comment, I figured out the reason why std::vector has value sementics (or why the inner array got copied).
If the copy constructor class is defined explicitly in the class definition, the copy constructor will determine how the struct/class instance is copied when the variable is passed in a function call. So I can define a copy constructor to my vector_int, in which I copy the whole inner_array, like
#include<iostream>
using namespace std;
class vector_int {
public:
int* inner_array;
int len;
vector_int(int *a, int len) {
inner_array = a;
this->len = len;
}
int* at(int pos) {
return inner_array+pos;
}
// this is the copy constructor
vector_int(const vector_int &v2) {
inner_array = new int;
for (int i =0; i < v2.len; i++) {
*(inner_array+i) = *(v2.inner_array+i);
}
}
};
// Yay, the vector_int's inner_array is copied
// when this function is called
// and no modification of the original vector is done
void modify(vector_int v) {
*(v.at(2)) = 10;
}
int main() {
int* a = new int[3] {1,2,3};
vector_int v = vector_int(a,3);
//
modify(v);
}
I checked the source code of the stdlib implementation on my local computer (g++ Apple LLVM version 10.0.0). The std::vector defines a copy constructor which looks like this
template <class _Tp, class _Allocator>
vector<_Tp, _Allocator>::vector(const vector& __x)
: __base(__alloc_traits::select_on_container_copy_construction(__x.__alloc()))
{
size_type __n = __x.size();
if (__n > 0)
{
allocate(__n);
__construct_at_end(__x.__begin_, __x.__end_, __n);
}
}
which looks like it does an malloc for the actual copied array + copy the array.
C++ allows class types to provide their own code for what it means to be created, copied, moved, and destroyed, and that code is called implicitly, without any obvious function calls. This is called value semantics and it's what C++ uses where other languages resort to things like create_foo(foo), foo.clone(), destroy_foo(foo) or foo.dispose().
Each class can define the following special member functions:
Constructors, for putting the object into a valid initial state
A Destructor, for cleaning up responsibly
A Copy Constructor, for creating a new object that is a duplicate of another
A Move Constructor, for creating a new object by transferring the data of another
A Copy Assignment Operator, for duplicating an object into an existing object
A Move Assignment Operator, for transferring data between two existing objects
These are all functions that you can define to do whatever you want. But they are called implicitly, meaning that users of such a class don't see these function calls in their code, and they expect them to do predictable things. You should make sure that your classes behave predictably by following the Rule of Three/Five/Zero.
There are of course other tools for sharing data, like pass-by-reference, which you already know about.
Lots of classes in the standard library use these special member functions to implement special behaviors that are very useful and help users write safe, correct code. For example:
std::vector when copied, will always have identical elements, though the underlying array and objects contained will be separate.
std::unique_ptr wraps a resource that has only one owner. To enforce this, it can't be copied.
std::shared_ptr wraps a resource that has many owners. It's not totally clear when to clean up such a resource, so copying a shared_ptr performs automatic reference counting, and the resource is cleaned up only when the last owner is done with it.
This is because vector has value semantics: when you copy it you get a true copy of all the elements.

Move Constructors and Static Arrays

I've been exploring the possibilities of Move Constructors in C++, and I was wondering what are some ways of taking advantage of this feature in an example such as below. Consider this code:
template<unsigned int N>
class Foo {
public:
Foo() {
for (int i = 0; i < N; ++i) _nums[i] = 0;
}
Foo(const Foo<N>& other) {
for (int i = 0; i < N; ++i) _nums[i] = other._nums[i];
}
Foo(Foo<N>&& other) {
// ??? How can we take advantage of move constructors here?
}
// ... other methods and members
virtual ~Foo() { /* no action required */ }
private:
int _nums[N];
};
Foo<5> bar() {
Foo<5> result;
// Do stuff with 'result'
return result;
}
int main() {
Foo<5> foo(bar());
// ...
return 0;
}
In this above example, if we trace the program (with MSVC++ 2011), we see that Foo<N>::Foo(Foo<N>&&) is called when constructing foo, which is the desired behaviour. However, if we didn't have Foo<N>::Foo(Foo<N>&&), Foo<N>::Foo(const Foo<N>&) would be called instead, which would do a redundant copy operation.
My question is, as noted in the code, with this specific example which is using a statically-allocated simple array, is there any way to utilize the move constructor to avoid this redundant copy?
First off, there's a general sort of advice that says you shouldn't write any copy/move constructor, assignment operator or destructor at all if you can help it, and rather compose your class of high-quality components which in turn provide these, allowing the default-generated functions to Do The Right Thing. (The reverse implication is that if you do have to write any one of those, you probably have to write all of them.)
So the question boils down to "which single-responsibility component class can take advantage of move semantics?" The general answer is: Anything that manages a resource. The point is that the move constructor/assigner will just reseat the resource to the new object and invalidate the old one, thus avoiding the (presumed expensive or impossible) new allocation and deep copying of the resource.
The prime example is anything that manages dynamic memory, where the move operation simply copies the pointer and sets the old object's pointer to zero (so the old object's destructor does nothing). Here's a naive example:
class MySpace
{
void * addr;
std::size_t len;
public:
explicit MySpace(std::size_t n) : addr(::operator new(n)), len(n) { }
~MySpace() { ::operator delete(addr); }
MySpace(const MySpace & rhs) : addr(::operator new(rhs.len)), len(rhs.len)
{ /* copy memory */ }
MySpace(MySpace && rhs) : addr(rhs.addr), len(rhs.len)
{ rhs.len = 0; rhs.addr = 0; }
// ditto for assignment
};
The key is that any copy/move constructor will do a full copying of the member variables; it is only when those variables are themselves handles or pointers to resources that you can avoid copying the resource, because of the agreement that a moved object is no longer considered valid and that you're free to steal from it. If there's nothing to steal, then there's no benefit in moving.
In this case it's not useful because int has no move-constructors.
However, it could be useful if those were strings instead, for example:
template<unsigned int N>
class Foo {
public:
// [snip]
Foo(Foo<N>&& other) {
// move each element from other._nums to _nums
std::move(std::begin(other._nums), std::end(other._nums), &_nums[0]);
}
// [snip]
private:
std::string _nums[N];
};
Now you avoid copying strings where a move will do. I'm not sure if a conforming C++11 compiler will generate equivalent code if you omit all the copy-/move-constructors completely, sorry.
(In other words, I'm not sure if std::move is specially defined to do an element-wise move for arrays.)
For the class template you wrote, there's no advantage to take in a move constructor.
There would be an advantage if the member array was allocated dynamically. But with a plain array as a member, there's nothing to optimize, you can only copy the values. There's no way to move them.
Usually, move-semantic is implemented when your class manages resource. Since in your case, the class doesn't manages resource, the move-semantic would be more like copy-semantic, as there is nothing to be moved.
To better understand when move-semantic becomes necessary, consider making _nums a pointer, instead of an array:
template<unsigned int N>
class Foo {
public:
Foo()
{
_nums = new int[N](); //allocate and zeo-initialized
}
Foo(const Foo<N>& other)
{
_nums = new int[N];
for (int i = 0; i < N; ++i) _nums[i] = other._nums[i];
}
Foo(Foo<N>&& other)
{
_nums = other._nums; //move the resource
other._nums=0; //make it null
}
Foo<N> operator=(const Foo<N> & other); //implement it!
virtual ~Foo() { delete [] _nums; }
private:
int *_nums;
};

Using a class with const data members in a vector

Given a class like this:
class Foo
{
const int a;
};
Is it possible to put that class in a vector? When I try, my compiler tells me it can't use the default assignment operator. I try to write my own, but googling around tells me that it's impossible to write an assignment operator for a class with const data members. One post I found said that "if you made [the data member] const that means you don't want assignment to happen in the first place." This makes sense. I've written a class with const data members, and I never intended on using assignment on it, but apparently I need assignment to put it in a vector. Is there a way around this that still preserves const-correctness?
I've written a class with const data members, and I never intended on using assignment on it, but apparently I need assignment to put it in a vector. Is there a way around this that still preserves const-correctness?
You have to ask whether the following constraint still holds
a = b;
/* a is now equivalent to b */
If this constraint is not true for a and b being of type Foo (you have to define the semantics of what "equivalent" means!), then you just cannot put Foo into a Standard container. For example, auto_ptr cannot be put into Standard containers because it violates that requirement.
If you can say about your type that it satisfies this constraint (for example if the const member does not in any way participate to the value of your object, but then consider making it a static data member anyway), then you can write your own assignment operator
class Foo
{
const int a;
public:
Foo &operator=(Foo const& f) {
/* don't assign to "a" */
return *this;
}
};
But think twice!. To me, it looks like that your type does not satisfy the constraint!
Use a vector of pointers std::vector<Foo *>. If you want to avoid the hassle of cleaning up after yourself, use boost::ptr_vector.
Edit: My initial stab during my coffee break, static const int a; won't work for the use case the OP has in mind, which the initial comments confirm, so I'm rewriting and expanding my answer.
Most of the time, when I want to make an element of a class constant, it's a constant whose value is constant for all time and across all instances of the class. In that case, I use a static const variable:
class Foo
{
public:
static const int a;
};
Those don't need to be copied among instances, so if it applied, that would fix your assignment problem. Unfortunately, the OP has indicated that this won't work for the case the OP has in mind.
If you want to create a read-only value that clients can't modify, you can make it a private member variable and only expose it via a const getter method, as another post on this thread indicates:
class Foo
{
public:
int get_a() const { return a; }
private:
int a;
};
The difference between this and
class Foo
{
public:
const int a;
};
is:
The const int gives you assurance that not even the implementation of the class will be able to muck with the value of a during the lifetime of the object. This means that assignment rightfully won't work, since that would be trying to modify the value of a after the object's been created. (This is why, btw, writing a custom operator=() that skips the copy of a is probably a bad idea design-wise.)
The access is different – you have to go through a getter rather than accessing the member directly.
In practice, when choosing between the two, I use read-only members. Doing so probably means you'll be able to replace the value of an object with the value of another object without violating semantics at all. Let's see how it would work in your case.
Consider your Grid object, with a width and height. When you initially create the vector, and let's say you reserve some initial space using vector::reserve(), your vector will be populated with initial default-initialized (i.e. empty) Grids. When you go to assign to a particular position in the vector, or push a Grid onto the end of the vector, you replace the value of the object at that position with a Grid that has actual stuff. But you may be OK with this! If the reason you wanted width and height to be constant is really to ensure consistency between width and height and the rest of the contents of your Grid object, and you've verified that it doesn't matter whether width and height are replaced before or after other elements of Grid are replaced, then this assignment should be safe because by the end of the assignment, the entire contents of the instance will have been replaced and you'll be back in a consistent state. (If the lack of atomicity of the default assignment was a problem, you could probably get around this by implementing your own assignment operator which used a copy constructor and a swap() operation.)
In summary, what you gain by using read-only getters is the ability to use the objects in a vector or any container with value semantics. However, it then falls to you to ensure that none of Grid's internal operations (or the operations of friends of Grid) violate this consistency, because the compiler won't be locking down the width and height for you. This goes for default construction, copy construction, and assignment as well.
I'm considering making the data member non-const, but private and only accessible by a get function, like this:
class Foo
{
private:
int a;
public:
int getA() const {return a;}
};
Is this 'as good' as const? Does it have any disadvantages?
As of c++20, using const member variables are legal without restrictions that had made it virtually unusable in containers. You still have to define a copy assignment member function because it continues to be automatically deleted when a const object exists in the class. However, changes to "basic.life" now allow changing const sub-objects and c++ provides rather convenient functions for doing this. Here's a description of why the change was made:
The following code shows how to define a copy assignment member function which is useable in any class containing const member objects and uses the new functions std::destroy_at and std::construct_at to fulfil the requirement so the new "basic.life" rules. The code demonstrates assignment of vectors as well as sorting vectors with const elements.
Compiler explorer using MSVC, GCC, CLANG https://godbolt.org/z/McfcaMWqj
#include <memory>
#include <vector>
#include <iostream>
#include <algorithm>
class Foo
{
public:
const int a;
Foo& operator=(const Foo& arg) {
if (this != &arg)
{
std::destroy_at(this);
std::construct_at(this, arg);
}
return *this;
}
};
int main()
{
std::vector<Foo> v;
v.push_back({ 2 });
v.push_back({ 1 });
v.insert(v.begin() + 1, Foo{ 0 });
std::vector<Foo> v2;
v2 = v;
std::sort(v2.begin(), v2.end(), [](auto p1, auto p2) {return p1.a < p2.a; });
for (auto& x : v2)
std::cout << x.a << '\n';
}