Undefined behavior for assignment operator - c++

Today I encountered following UB:
std::vector<double> operator -(const std::vector<double>& this_, const std::vector<double>& other_){
std::vector<double> result;
for(int i = 0; i < this_.size(); i++){
result[i] = this_[i] - other_[i];
}
return result;
}
Basically what I think is when I declare the result the default constructor is called, but it does not initialize the object to a valid state, so when I try to access the element using result[i], it causes undefined behavior.
So my question is this: Does lhs have operation priority over rhs? If not, how can this be UB if it evaluates the rhs first and sets lhs to rhs?

Related

Proper way to facilitate MOVE operation when overriding operator on C++

I'm not really familiar with how move works in C++, and I need some help to clarify my understanding. I'm want to overload the operator+, and I'm have a couple of questions about it.
ap_n operator+(const ap_n& n, const ap_n& m) {
ap_n tmp {n};
return tmp += m;
}
My first question is how to make temporary objects movable. As shown in my function above, both arguments are not meant to be mutated, therefore I need to create a third object to do the operation upon.
How can I make my return value usable for move operation. Is the return value supposed to be a reference as ap_n&? Should the return object be encapsulated by std::move(tmp)? Or is it alright as is?
How does C++ decide when an object is rvalue, or decide that a move operation is suitable on an object, and how can I tell the program that an object is safe to use for move operation.
ap_n operator+(const ap_n&, const ap_n&); // defined as in first question
ap_n operator+(ap_n&& n, const ap_n& m) { return n += m; }
ap_n operator+(const ap_n& n, ap_n&& m) { return m += n; }
ap_n operator+(ap_n&& n, ap_n&& m) { return n += m; }
My second question is whether it is necessary to create variants of function that accept rvalue arguments. Right now I have 4 functions, as shown, to be able to accept normal objects and rvalue objects.
Is writing all the combinations possible like this necessary? If I remove all but the first function, would the program still be able to perform move operation correctly?
As a debugging tip, something that can help with getting these things right is to print a message in the move constructor
ap_n(ap_n&& o): x_(std::move(o.x_)) { std::cerr << "Move constructed\n"; }
plus similar messages in other constructors and the destructor. Then you get a clear chronology of when and how instances are created and destroyed.
How can I make my return value usable for move operation. Is the return value supposed to be a reference as ap_n&? Should the return object be encapsulated by std::move(tmp)? Or is it alright as is?
Return the result by value. (Don't return a reference to a local variable, since the local variable goes out of scope immediately, making the reference invalid.) You might find this short article useful: Tip of the Week #77: Temporaries, Moves, and Copies. For more depth, check out cppreference on copy elision.
how to make temporary objects movable
By defining move constructor/assignment to your type.
Is the return value supposed to be a reference as ap_n&?
Not for operator+ when you return new object.
operator += on the other hand returns reference to lhs, so returns ap_n&.
How can I make my return value usable for move operation. Should the return object be encapsulated by std::move(tmp)? Or is it alright as is?
From return statement,
there is an automatic move when returning directly a local variable.
so return tmp; is sufficient.
return std::move(tmp); prevents NRVO
return tmp += m; does a copy, as you don't return "directly" tmp.
You should do:
ap_n operator+(const ap_n& n, const ap_n& m) {
ap_n tmp {n};
tmp += m;
return tmp; // NRVO, or automatic move
}
return std::move(tmp += m); would prevent NRVO, and do the move.
How does C++ decide when an object is rvalue,
Roughly,
variables are l-value as there have name.
function returning l-value reference (ap_n&) returns l-value.
function returning r-value reference (ap_n&&), or by value (ap_n) returns r-value.
or decide that a move operation is suitable on an object, and how can I tell the program that an object is safe to use for move operation.
Overload resolution select the best match between valid candidate.
So it requires function taking by value or by r-value reference (or forwarding reference).
My second question is
It seems not the second ;-)
whether it is necessary to create variants of function that accept rvalue arguments. Right now I have 4 functions, as shown, to be able to accept normal objects and rvalue objects.
Is writing all the combinations possible like this necessary?
Single function taking by const reference or by value can be enough in general case,
unless you want that optimization. so mostly for library writer, or critical code.
Notice that your overloads should be rewritten to effectively do move operation (to reuse input temporary parameter):
ap_n operator+(ap_n&& n, const ap_n& m)
{
n += m;
return std::move(n); // C++11; C++14, C++17
return n; // C++20
}
or
ap_n&& operator+(ap_n&& n, const ap_n& m)
{
return std::move(n += m);
}
If I remove all but the first function, would the program still be able to perform move operation correctly?
With
ap_n operator+(const ap_n& n, const ap_n& m) {
ap_n tmp {n}; // copy
tmp += m;
return tmp; // NRVO, or automatic move
}
You have 1 copy, one NRVO/move for any kind of parameters.
With
ap_n&& operator+(ap_n&& n, const ap_n& m) {
return std::move(n += m);
}
you have no moves, but you should be careful about lifetime of references, as
auto ok = ap_n() + ap_n(); // 1 extra move
auto&& dangling = ap_n() + ap_n(); // No move, but no lifetime extension...
With
ap_n operator+(ap_n&& n, const ap_n& m) {
n += m;
return std::move(n); // C++11, C++14, C++17 // move
return n; // C++20 // automatic move
}
you have 1 move, no copies.
auto ok = ap_n() + ap_n(); // 1 extra move possibly elided pre-C++17, 0 extra moves since C++17
auto&& ok2 = ap_n() + ap_n(); // No moves, and lifetime extension...
So with extra overload, you might trade copy to move.
Taking notes from Jarod42's answer, the following is the revised code.
ap_n operator+(const ap_n& n, const ap_n& m) {
ap_n tmp {n};
tmp += n;
return tmp; // Allows copy, NRVO, or move
}
ap_n operator+(ap_n&& n, const ap_n& m) {
n += m;
return std::move(n); // Allows copy, or move
}
ap_n operator+(const ap_n& n, ap_n&& m) {
m += n;
return std::move(m); // Allows copy, or move
}
The amount of functions is also reduced to 3, since one that takes both rvalue reference will automatically use the second function.
Please tell me if I'm still misunderstanding this.

Move assignment in overloaded vector summation

I am going over A Tour of C++ (Section 5.2 Copy and Move). Following the instructions in the book, I have built a container called Vector (it mimics std::vector). My goal is to efficiently implement the following (element-wise) summation:
Vector r = x + y + z;
According to the book, if I don't have move assignment and constructor, + operator will end up copying Vectors unnecessarily. So I implemented move assignment and constructor, but I think the compiler still doesn't use them when I run Vector r = x + y + z;. What am I missing? I appreciate any feedback. Below is my code. I expect to see an output Move assignment, but I get no output. (The summation part works, it's just the move business that I am not sure)
Code
// Vector.h
class Vector{
public:
explicit Vector(int);
Vector(std::initializer_list<double>);
// copy constructor
Vector(const Vector&);
// copy assignment
Vector& operator=(const Vector&);
// move constructor
Vector(Vector&&);
// move assignment
Vector& operator=(Vector&&);
~Vector(){delete[] elem;}
double& operator[](int) const;
int size() const;
void show();
friend std::ostream& operator<< (std::ostream& out, const Vector& vec);
private:
int sz;
double* elem;
};
Vector operator+(const Vector&,const Vector&);
// Vector.cpp
Vector::Vector(std::initializer_list<double> nums) {
sz = nums.size();
elem = new double[sz];
std::initializer_list<double>::iterator it;
int i = 0;
for (it=nums.begin(); it!=nums.end(); ++it){
elem[i] = *it;
++i;
}
}
Vector::Vector(Vector&& vec) {
sz = vec.sz;
vec.sz = 0;
elem = vec.elem;
vec.elem = nullptr;
std::cout<<"Move constructor"<<std::endl;
}
Vector& Vector::operator=(Vector&& vec) {
if (this == &vec){
return *this;
}
sz = vec.sz;
vec.sz = 0;
elem = vec.elem;
vec.elem = nullptr;
std::cout<<"Move assignment"<<std::endl;
return *this;
Vector operator+(const Vector& vec1, const Vector& vec2){
if (vec1.size() != vec2.size()){
throw std::length_error("Input vectors should be of the same size");
}
Vector result(vec1.size());
for (int i=0; i!=vec1.size(); ++i){
result[i] = vec1[i]+vec2[i];
}
return result;
}
}
// Main
int main() {
Vector x{1,1,1,1,1};
Vector y{2,2,2,2,2};
Vector z{3,3,3,3,3};
Vector r = x + y + z;
} // Here I expect the output: Move assignment, but I get no output.
There is a move elision takes place.
According to the C++ 17 Standard (12.8 Copying and moving class objects)
31 When certain criteria are met, an implementation is allowed to omit
the copy/move construction of a class object, even if the constructor
selected for the copy/move operation and/or the destructor for the
object have side effects. In such cases, the implementation treats
the source and target of the omitted copy/move operation as simply two
different ways of referring to the same object, and the destruction of
that object occurs at the later of the times when the two objects
would have been destroyed without the optimization.122 This elision of
copy/move operations, called copy elision, is permitted in the
following circumstances (which may be combined to eliminate multiple
copies):
(31.3) — when a temporary class object that has not been bound to a
reference (12.2) would be copied/moved to a class object with the same
cv-unqualified type, the copy/move operation can be omitted by
constructing the temporary object directly into the target of the
omitted copy/move
So the move constructor is omitted. The second temporary object created by the expression x + y + z; is directly built as the obejct r.
Vector r = x + y + z;
Also take into account that there is move elision in the return statement of the operator
(31.1) — in a return statement in a function with a class return type,
when the expression is the name of a non-volatile automatic object
(other than a function or catch-clause parameter) with the same
cvunqualified type as the function return type, the copy/move
operation can be omitted by constructing the automatic object directly
into the function’s return value
So the temporary object created in the expression x + y (within the operator) will be moved (that is there will be elision relative to the return statement - preceding quote) in the expression ( x + y ) + z where it will be used by constant lvalue reference and the new temporary obejct created in this expression will be built as r.
In whole in this code snippet
Vector x{1,1,1,1,1};
Vector y{2,2,2,2,2};
Vector z{3,3,3,3,3};
Vector r = x + y + z;
due to the move elision there will be created 5 objects of the class Vector.

Error in that.vect.push_back(0) and that.vect.begin() in overloading "+" orerator in C++

class Polinom {
public:
std::vector<double> vect;
Polinom operator +(const Polinom &that) {
if (this->vect.size() > that.vect.size()) {
for (int i = that.vect.size(); i < this->vect.size(); i++)
that.vect.push_back(0);//here
}
else if (that.vect.size() > this->vect.size()) {
for (int i = this->vect.size(); i < that.vect.size(); i++)
this->vect.push_back(0);
}
std::vector<double> sum;
std::vector<double>::iterator i2 = that.vect.begin();//here
for (std::vector<double>::iterator i1 = this->vect.begin(); i1 != this->vect.end(); ++i1)
sum.push_back(*i2++ + *i1);
return sum;
}
Polinom();
Polinom(std::vector<double> vect) {
this->vect = vect;
}
~Polinom();
};
Severity Code Description Project File Line Suppression State
Error (active) E1087 no instance of overloaded function "std::vector<_Ty, _Alloc>::push_back [with _Ty=double, _Alloc=std::allocator]" matches the argument list and object (the object has type qualifiers that prevent a match)
Polinom operator +(const Polinom &that) {
^^^^^
that is a const reference.
that.vect.push_back(0);//here
Since that is const, so are member access expressions through that reference. Thus that.vect is a const expression.
push_back is a non-const member function. It modifies the vector. The function cannot be called on const vectors.
std::vector<double>::iterator i2 = that.vect.begin();//here
std::vector<T>::begin() const returns a std::vector<T>::const_iterator. It is not implicitly convertible to std::vector<T>::iterator
Solution 1: Don't attempt to push elements into const vectors.
Solution 2: Use a non-const reference when you intend to modify the referred object.
In this case, solution 1. seems to be more sensible. Modifying operands of operator+ would be counter intuitive. Furthermore, you should probably make the function const qualified, and avoid modifying this, for the same reason.
That is constant, so you can't pushback into it. Not sure what you want to do there. Figure out if you want to modify that or not, and pass either a & or a const&.
And that is constant, you can't get an iterator, only a const_iterator (auto would capture it properly).

Operator Overloading with Constant Iterators

I have a constant iterator class which contains the following methods for overloading several operator functions
self_reference operator=( const SDAL_Const_Iter& src ) {
index = src.index;
return *this;
}
self_reference operator++() {
index = index + 1;
return *this;
}
self_type operator++(int) {
SDAL_Const_Iter results = *this;
++index;
return results;
}
The index variable is of type const int.
My compiler is complaining that I am attempting to modify a constant object (More specifically, "Error C2166: l-value specifies constant object"), which I am aware of; however, I see no other way of overloading these functions. Can someone please elaborate on how to go about writing these overloads without causing compiler issues?
I believe the problem is in const int as index variable.
A constant iterator should not allow non-const access to the container's data. The iterator itself, though, is changeable (it has to be able to iterate). Changing index to int should fix the problem.

invalid initialization of non-const reference

Alright, I'm trying to figure out this error and have, so far, had absolutely no luck. I'm doing this for homework, which is why I'm not using included classes.
Here's the relevant code:
//Will return an array where each element is the sum of the respective sums in the added arrays
Vec& Vec::operator+(Vec& v2) const{
Vec ret();
if(mySize>0){
Vec ret(mySize);
for(unsigned i = 0;i<mySize;i++){
ret[i]=v2[i]+myArray[i];
}
}
else{
}
return ret;
}
And from the .h file...:
Vec& operator+ (Vec& v2) const;
This throws the error: "invalid initialization of non-const reference of type ‘Vec&’ from an rvalue of type ‘Vec (*)()’"
I'm completely new to C++, so any help would be appreciated.
Vec ret();
Is taken to be a forward declaration of a function which takes no arguments and returns a Vec. See: the most vexing parse.
Next, you're returning a reference to a local variable, which is bad. ret goes out of scope as soon as the function returns.
The actual error is that you are declaring a function inside of your operator, instead of declaring a Vec object.
Vec ret();
You can fix that by omitting the ():
Vec ret;
Besides that, you have a fundamental design error in that you are attempting to return a reference to a variable which is local to the scope of your operator, resulting in a dangling reference. The usual way to express an addition operator is to have it return a new object, and is typically implemented as a non-member function with a signature such as
Vec operator+(const Vec& lhs, const Vec& rhs);
This can be implemented in terms of an increment member operator
Vec& operator+=(const Vec& rhs);
This one can return a reference to this hence no dangling reference. An example implementation od operator+ would then be
Vec operator+(Vec lhs, const Vec& rhs)
{
return lhs += rhs;
}
As others have stated your initial declaration of ret which you think is default constructing a Vec is actually forward delcaring a function which takes no arguments and returns a Vec. AKA the most vexing parse.
Also the variable shadowing of 'ret' inside the if statement means that you are not modifying the variable that you are expecting to return. It's likely that you want something more like this:
Vec Vec::operator+(const Vec& v2) const{
Vec ret(mySize);
if(mySize>0){
for(unsigned i = 0;i<mySize;i++){
ret[i]=v2[i]+myArray[i];
}
}
else{
}
return ret;
}