Hello,
Given a container C of value_type T, the expected behavior when applying an action on the container is to see that action distributed over its elements.
-Copying the container->copies each elem.
-Destroying the container->destroys each elem.
-Moving the container-> moves each elem.
(Moving here in the sense moving from one memory location to another one).
By extrapolation:
-Applying an action-> applys it on each elem.
...
Yet there is one fundamental action that doesn't comply to this mental model:
Assignment !
When you assign one container to another, it is not guaranteed that all elements use Assignment operation.
C<T> v1;
v1 = v2; // or = std::move(v2);
Usually the implementation distributes copy-construction, or move-construction operation on each element instead of copy/move assignment operator.
And sometimes you see a mixture of both (construction and assignment)
Let say you have:
#include <vector>
#include <iostream>
struct A{
A(){ std::puts(__PRETTY_FUNCTION__);}
A(const A&)
{ std::puts(__PRETTY_FUNCTION__);}
A(A&&)
{ std::puts(__PRETTY_FUNCTION__);}
A& operator=(const A&) = default;//elete;
A& operator=(A&&)= default;//elete;
};
int main()
{
std::vector<A> v1(5);
std::vector<A> v2;
std::puts("-----");
v2 = v1;
return 0;
}
######
Output:
A::A()
A::A()
A::A()
A::A()
A::A()
-----
A::A(const A&)
A::A(const A&)
A::A(const A&)
A::A(const A&)
A::A(const A&)
As you can see...
The mental model of distributing actions over each element is not respected for the copy assignment operation, instead of no log after the "-----" above, we see "A::A(const A&)".
The proposal:
To keep the mental model of "action distribution over container elements", i suggest adding the capability of "construction by assignment"
That is, we can construct an object directly by copy assignment or move assignment.
And the following snippet should not be UB.
///
T* p = (T*)malloc(sizeof(T));
T val;
*p = val; // UB here
///
for all T's either trivial or non trivial constructible.
One suggestion is to add 2 new special functions
T& operator = (std::construct_tag, const T&);
T& operator = (std::construct_tag, T&&);
Where the implementation just delegates the construction to the appropriate copy/move constructor. Expl
T& operator = (std::construct_tag, const T& t)
: T(t)
{ }
T& operator = (std::construct_tag, T&& t)
: T(std::move(t))
{ }
The only problemt is : how to detect that the object is being constructed by assignment i.e how to choose one of the above new special functions?
I mean:
///
T t1, t2;
T* p;
T* q = &t1;
*p = t2; // must pick construct by copy assign
*q = t2; // must pick regular copy assign
///
Over to you.
Thanks