C++ Template Assignment Operator Overloading

Overloaded assignment operator for class template objects

I designed a class template to create unique arrays. I was able to successfully input data to and output data from my array objects, irrespective of the datatype. However, I can't for the life of me fathom why my overloaded assignment operator worked perfectly well only for integer datatype and not for double/string datatypes.


Here is the class definition:




And here is the definition of the overloaded assignment operator:




And here is my main function that tests the operations on objects of the class:



The problem I'm having starts from where the assignment operator is being tested: for double and string datatypes, the upper input/output section works fine, but the assignment section freezes the display until the program execution is manually terminated!!

Any helpful insights would be highly appreciated. Please feel free to ask or request for clarifying info about the code. Thanks as I eagerly await your responses.
What if lower bound is 9 and upper bound is 100? You will create an array of size 1, but will try to access 99th element.
Also problem with negative elements. Remember: arratPtr[i] will not use overloaded operator[].
If it works for ints, I think you have some problems with operator[] too. Show it.
Thanks MiiNiPaa for your prompt reply. Let me try to respond:

You wrote: "What if lower bound is 9 and upper bound is 100? You will create an array of size 1, but will try to access 99th element."
Did you mean to write 10 and not 100? 'Cause that's the only way the array size would result in 1.

Also you wrote: "Also problem with negative elements." Did you mean negative elements of the array OR negative parameters at object declaration? Anyways, the index of arrayPtr (= i) can never be negative, given some object declaration constraint (e.g. upperBound>lowerBound) and the way I wrote the constructor.

The constructor is definitely not the problem; like I implied in my earlier post, the objects of the class were successfully created (for integer, double, and string datatypes); it's the assignment operation that's failing me, I'm guessing. Here are my constructor and the overloaded operator[] you asked for:






By the way, I'm jealous of the way you captured the exact fonts/color of my program segment. Yours even had line numbers! So cool! How do I go about doing that on here; I'm fairly new to the forum. Thanks.


There is code tags button. It looks like <>.

Did you mean to write 10 and not 100?
I meant 99 :)

In your constructor: if you pass (-10, 0) as parameters, it will create array of size 10 with valid indexes [0,9]
But if you call yoir object as x[-8] it will chech that it is between lower and upper bound (which is true) and will try to do , which is dangerous and can lead to crash.
You probably intended to normalize your range and access underlying array as
What is the problem with the copy assignment operator? I would decalre it without qualifier const in the return type




As for me I would make arrayUpperBound acceptable to specify as an index.
I wouldn't use copy, I'd use move instead such that if the object has a copy constructor for a rvalue reference then you will be saying time instead of allocating extra memory in the copy.

I wouldn't use copy, I'd use move instead


Logically that wouldn't make sense. This is copy assignment, not move assignment. The elements to be moved are const and thus cannot be "moved" as doing so requires the source elements to be modified.


[edit: typo'd]

@MiiniPaa: I see your point about the dangers of problematic "extreme" parameters during object declaration. However, the point still remains that I did not use those extreme parameter cases in my first post above that worked for integer datatypes but failed for double/string datatypes. I have already designed a more versatile constructor to take care of those extreme parameters at object declarations. It just boggles the mind that the same set of parameters that worked for integers would not work for double/string datatypes.

@vlad from moscow: Let's assume the "const" in the return type were the problem, how come this did not affect the integer datatype runs I did, all of whose assignment operations were successfully implemented??

@xerzi: Well, the "Law of the Big Three" stipulates inclusion of the copy constructor in a class with pointer data members.

@cire: I think I agree with your objection to xerzi's post.


Still looking forward to any helpful insights. Thank you all.


I did not know what is your problem with the copy assignment operator because you did not say nothing about it.
@vlad from moscow:
I did not know what is your problem with the copy assignment operator because you did not say nothing about it.

If you examine my first post in which I show function main, you would see four assignment statements in lines 40, 45, 50 & 55. These are the lines that are giving me a headache. Like I said in my first post, these assignment statements are successfully achieved when the objects are of integer datatype. However, program execution halts if the objects are of double/string datatypes. That's why it makes sense to think that my overloaded assignment statement is the problem. But from a theoretical standpoint, I can't see where my overloaded assignment operator function is wrong.


@cire: I'll try to go over that website you suggested.

Thanks all! The search for a reason for this datatype-based execution anomaly continues.
I would guess that, since every function you've shown so far has handled indexing your array incorrectly (ie: not adjusting the index to be zero-based before using it to access the array), by the time you get to the later tests you've been steadily trashing memory and the results of that finally show up.

@cire:
You wrote:
I would guess that, since every function you've shown so far has handled indexing your array incorrectly (ie: not adjusting the index to be zero-based before using it to access the array), ...


Well that's the point of the task I embarked upon: to create non-conventional (not necessarily zero-based) arrays that are strictly based on the definition of the class to which they belong. I beg to differ that the indexing is being handled incorrectly because my overloaded array index operator function(definition shown in an earlier post) has already taken care of the non-zero-based feature of the objects of the class.
Read my posts again.
If you create it will create array with one element.
If you do it will pass your checks, but will access 100th one which is overflow

If you create it will create array with ten elements.
If you do it will pass your checks, but will access -8th element, which is invalid for arrays.

Look what your function doing in these cases step by step and you will probably understand that yourself
@MiiNiPaa & @cire:

I think I'm starting to see your points about the array indexing angle to my problem. For some reason, I have apparently and erroneously overlooked the fact that within the function definition of the operator I'm trying to overload, the primary ANSI C++ definition of that same operator is still preserved.

MiiNiPaa's examples sort of illustrate this: in main function, given the nature of my class, it makes sense to have y[-8] (i.e. trying to access an array component of object y); however, within the overloaded operator function's definition for [], this array component reference is gibberish!!

I'll go and explore this dimension to the problem and let you guys know if it resolves it. In the meantime, thanks.
MiiNiPaa wrote:
You probably intended to normalize your range and access underlying array as
@MiiNiPaa:

Oh yeah! Although I had already come up with arrayPtr[index + (-1*arrayLowerBound)], essentially the same as yours. It's funny how even though I read all your prior posts, the epiphany I had on the indexing pitfall of my code did not occur until my last post. I observed you caught this pretty early on!

After making the necessary adjustments, I observed a marginal improvement in program behavior; however, I now see some strange, random, large numbers in the assignment section that I did not input in the first place! This aberrant behavior even for integer datatypes that I thought were in the clear before. Of course, the object input/output section for all datatypes still works fine; the assignment section of the code is where things go awry.

I have included a sample program output here for your observation:






Any insights?

If that wasn't changed, you got same problem here. Either use your array subscript operator to handle this, or corrext indexes manually.
Like I said, I already made all the adjustments related to the array indexing throughout my class' member functions.

A move assignment operator of class is a non-template non-static member function with the name operator= that takes exactly one parameter of type T&&, const T&&, volatile T&&, or constvolatile T&&.

[edit]Syntax

class_nameclass_name ( class_name ) (1) (since C++11)
class_nameclass_name ( class_name ) = default; (2) (since C++11)
class_nameclass_name ( class_name ) = delete; (3) (since C++11)

[edit]Explanation

  1. Typical declaration of a move assignment operator.
  2. Forcing a move assignment operator to be generated by the compiler.
  3. Avoiding implicit move assignment.

The move assignment operator is called whenever it is selected by overload resolution, e.g. when an object appears on the left-hand side of an assignment expression, where the right-hand side is an rvalue of the same or implicitly convertible type.

Move assignment operators typically "steal" the resources held by the argument (e.g. pointers to dynamically-allocated objects, file descriptors, TCP sockets, I/O streams, running threads, etc.), rather than make copies of them, and leave the argument in some valid but otherwise indeterminate state. For example, move-assigning from a std::string or from a std::vector may result in the argument being left empty. This is not, however, a guarantee. A move assignment is less, not more restrictively defined than ordinary assignment; where ordinary assignment must leave two copies of data at completion, move assignment is required to leave only one.

[edit]Implicitly-declared move assignment operator

If no user-defined move assignment operators are provided for a class type (struct, class, or union), and all of the following is true:

  • there are no user-declared copy constructors;
  • there are no user-declared move constructors;
  • there are no user-declared copy assignment operators;
  • there are no user-declared destructors;
  • the implicitly-declared move assignment operator would not be defined as deleted,
(until C++14)

then the compiler will declare a move assignment operator as an member of its class with the signature .

A class can have multiple move assignment operators, e.g. both T& T::operator=(const T&&) and T& T::operator=(T&&). If some user-defined move assignment operators are present, the user may still force the generation of the implicitly declared move assignment operator with the keyword .

The implicitly-declared (or defaulted on its first declaration) move assignment operator has an exception specification as described in dynamic exception specification(until C++17)exception specification(since C++17)

Because some assignment operator (move or copy) is always declared for any class, the base class assignment operator is always hidden. If a using-declaration is used to bring in the assignment operator from the base class, and its argument type could be the same as the argument type of the implicit assignment operator of the derived class, the using-declaration is also hidden by the implicit declaration.

[edit]Deleted implicitly-declared move assignment operator

The implicitly-declared or defaulted move assignment operator for class is defined as deleted if any of the following is true:

  • has a non-static data member that is const;
  • has a non-static data member of a reference type;
  • has a non-static data member that cannot be move-assigned (has deleted, inaccessible, or ambiguous move assignment operator);
  • has direct or virtual base class that cannot be move-assigned (has deleted, inaccessible, or ambiguous move assignment operator);
  • has a non-static data member or a direct or virtual base without a move assignment operator that is not trivially copyable;
  • has a direct or indirect virtual base class.
(until C++14)

A deleted implicitly-declared move assignment operator is ignored by overload resolution.

(since C++14)

[edit]Trivial move assignment operator

The move assignment operator for class is trivial if all of the following is true:

  • It is not user-provided (meaning, it is implicitly-defined or defaulted);
  • has no virtual member functions;
  • has no virtual base classes;
  • the move assignment operator selected for every direct base of is trivial;
  • the move assignment operator selected for every non-static class type (or array of class type) member of is trivial;
  • has no non-static data members of volatile-qualified type.
(since C++14)

A trivial move assignment operator performs the same action as the trivial copy assignment operator, that is, makes a copy of the object representation as if by std::memmove. All data types compatible with the C language (POD types) are trivially move-assignable.

[edit]Implicitly-defined move assignment operator

If the implicitly-declared move assignment operator is neither deleted nor trivial, it is defined (that is, a function body is generated and compiled) by the compiler if odr-used.

For union types, the implicitly-defined move assignment operator copies the object representation (as by std::memmove).

For non-union class types (class and struct), the move assignment operator performs full member-wise move assignment of the object's direct bases and immediate non-static members, in their declaration order, using built-in assignment for the scalars, memberwise move-assignment for arrays, and move assignment operator for class types (called non-virtually).

As with copy assignment, it is unspecified whether virtual base class subobjects that are accessible through more than one path in the inheritance lattice, are assigned more than once by the implicitly-defined move assignment operator:

struct V { V& operator=(V&& other){// this may be called once or twice// if called twice, 'other' is the just-moved-from V subobjectreturn*this;}};struct A :virtual V {};// operator= calls V::operator=struct B :virtual V {};// operator= calls V::operator=struct C : B, A {};// operator= calls B::operator=, then A::operator=// but they may only called V::operator= once   int main(){ C c1, c2; c2 = std::move(c1);}
(since C++14)

[edit]Notes

If both copy and move assignment operators are provided, overload resolution selects the move assignment if the argument is an rvalue (either a prvalue such as a nameless temporary or an xvalue such as the result of std::move), and selects the copy assignment if the argument is an lvalue (named object or a function/operator returning lvalue reference). If only the copy assignment is provided, all argument categories select it (as long as it takes its argument by value or as reference to const, since rvalues can bind to const references), which makes copy assignment the fallback for move assignment, when move is unavailable.

It is unspecified whether virtual base class subobjects that are accessible through more than one path in the inheritance lattice, are assigned more than once by the implicitly-defined move assignment operator (same applies to copy assignment).

See assignment operator overloading for additional detail on the expected behavior of a user-defined move-assignment operator.

[edit]Example

Run this code

Output:

#include <string>#include <iostream>#include <utility>   struct A {std::string s; A(): s("test"){} A(const A& o): s(o.s){std::cout<<"move failed!\n";} A(A&& o): s(std::move(o.s)){} A& operator=(const A& other){ s = other.s;std::cout<<"copy assigned\n";return*this;} A& operator=(A&& other){ s = std::move(other.s);std::cout<<"move assigned\n";return*this;}};   A f(A a){return a;}   struct B : A {std::string s2;int n;// implicit move assignment operator B& B::operator=(B&&)// calls A's move assignment operator// calls s2's move assignment operator// and makes a bitwise copy of n};   struct C : B { ~C(){}// destructor prevents implicit move assignment};   struct D : B { D(){} ~D(){}// destructor would prevent implicit move assignment D& operator=(D&&)=default;// force a move assignment anyway };   int main(){ A a1, a2;std::cout<<"Trying to move-assign A from rvalue temporary\n"; a1 = f(A());// move-assignment from rvalue temporarystd::cout<<"Trying to move-assign A from xvalue\n"; a2 = std::move(a1);// move-assignment from xvalue   std::cout<<"Trying to move-assign B\n"; B b1, b2;std::cout<<"Before move, b1.s = \""<< b1.s<<"\"\n"; b2 = std::move(b1);// calls implicit move assignmentstd::cout<<"After move, b1.s = \""<< b1.s<<"\"\n";   std::cout<<"Trying to move-assign C\n"; C c1, c2; c2 = std::move(c1);// calls the copy assignment operator   std::cout<<"Trying to move-assign D\n"; D d1, d2; d2 = std::move(d1);}
Trying to move-assign A from rvalue temporary move assigned Trying to move-assign A from xvalue move assigned Trying to move-assign B Before move, b1.s = "test" move assigned After move, b1.s = "" Trying to move-assign C copy assigned Trying to move-assign D move assigned

0 Thoughts to “C++ Template Assignment Operator Overloading

Leave a comment

L'indirizzo email non verrà pubblicato. I campi obbligatori sono contrassegnati *