overloaded assignment operator and different data types

Pages: 12
Simeonz: that is truly an impressive piece of work. I downloaded it and ran it, and it works beautifully. I would use it, but...there's some stuff in there I don't understand.

Hamsterman: you said above:
You can't overload int::operator =, but you can write reg::operator int()


I'm not sure I understand this. Let me recap what I'm trying to do:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class reg
{
    int c, n;
public:
.
.
.
}

reg r;
int i;

i = r; // should basically do an i = r.c;
r = i; // should basically do an r.n = i; 


So, is there a way to do this without resorting to the elaborate stuff that Simeonz put together?

Thanks.
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Integer{
   int i;
   Integer() : i(0) {}
   Integer(int i) : i(i) {}
   Integer& operator = (int n){
      i = n;
      return *this;
   }
   operator int(){
      return i;
   }
};
int main(){
   Integer a(3), b = 7;
   b = 5;
   int c = a;
   return 0;
}
Simeonz: I'm finding myself drawn to your solution. I tried adapting it for my register, which contains two integers. Most of it seems to work, but I tried to add a default constructor and it failed. Any idea why? Here's the code, without the main function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <QtCore/QCoreApplication>

#include <iostream>

class Register {
private:
  int qCurrent;
  int qNext;
  int getValue() const { return qCurrent; };

  Register& setValue(int value)
  {
    qNext = value;
    return *this;
  }

public:
  Register() { qCurrent = 0; qNext = 0; } // this is the constructor I added
  explicit Register(int c, int n) : qCurrent(c), qNext(n) {}

  Register& operator= (const Register& reg)
  {
    qNext = reg.qNext;
    return *this;
  }

  operator int() const { return getValue(); }
  Register& operator= (int value) { return setValue(value); }
  Register& operator+= (int value) { return setValue((int)(*this) + value); }
  Register& operator-= (int value) { return setValue((int)(*this) - value); }
  Register& operator*= (int value) { return setValue((int)(*this) * value); }
  Register& operator/= (int value) { return setValue((int)(*this) / value); }
  Register& operator%= (int value) { return setValue((int)(*this) % value); }
  Register& operator&= (int value) { return setValue((int)(*this) & value); }
  Register& operator|= (int value) { return setValue((int)(*this) | value); }
  Register& operator^= (int value) { return setValue((int)(*this) ^ value); }
  Register& operator>>= (int value) { return setValue((int)(*this) >> value); }
  Register& operator<<= (int value) { return setValue((int)(*this) << value); }

  void print();
};

void Register::print()
{
  std::cout << std::hex << "qCurrent: " << qCurrent << ". qNext: " << qNext << std::endl;
}


Thanks. Oh, BTW: you had your bitshift functions reversed; corrected in my copy.
Last edited on
I really try to get things done right, but they unhinge very often - like the bitshift. :)

Anyway. The following code compiles ok and runs ok with me:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <QtCore/QCoreApplication>

#include <iostream>

class Register {
private:
  int qCurrent;
  int qNext;
  int getValue() const { return qCurrent; };

  Register& setValue(int value)
  {
    qNext = value;
    return *this;
  }

public:
  Register() { qCurrent = 0; qNext = 0; } // this is the constructor I added
  explicit Register(int c, int n) : qCurrent(c), qNext(n) {}

  Register& operator= (const Register& reg)
  {
    qNext = reg.qNext;
    return *this;
  }

  operator int() const { return getValue(); }
  Register& operator= (int value) { return setValue(value); }
  Register& operator+= (int value) { return setValue((int)(*this) + value); }
  Register& operator-= (int value) { return setValue((int)(*this) - value); }
  Register& operator*= (int value) { return setValue((int)(*this) * value); }
  Register& operator/= (int value) { return setValue((int)(*this) / value); }
  Register& operator%= (int value) { return setValue((int)(*this) % value); }
  Register& operator&= (int value) { return setValue((int)(*this) & value); }
  Register& operator|= (int value) { return setValue((int)(*this) | value); }
  Register& operator^= (int value) { return setValue((int)(*this) ^ value); }
  Register& operator>>= (int value) { return setValue((int)(*this) >> value); }
  Register& operator<<= (int value) { return setValue((int)(*this) << value); }

  void print();
};

void Register::print()
{
  std::cout << std::hex << "qCurrent: " << qCurrent << ". qNext: " << qNext << std::endl;
}

int main(void)
{
  Register x;
  x.print();

  Register y(1, 1);
  y.print();

  x = 2;
  x.print();

  x = x + 1;
  x.print();

  x += 1;
  x.print();

  x += y;
  x.print();

  x = y;
  x.print();
}

A few comments on semantics. The "explicit" keyword applies to conversion constructors. Such is the name of constructors with a single argument, or single mandatory argument (if the remaining arguments have default values). When the compiler needs value of type X in some expression (function call, operation, etc.), but instead is given value of type Y, it tries to convert Y to X. One way to do that is to construct X with Y as constructor argument. This is why those are called conversion constructors.

In your case, I thought that you will not want to convert integers to registers implicitly. This may look like possible source of simplification, but one integer a register does not make, so to speak. So I marked the constructor with the "explicit" keyword, which prohibits the conversion through this constructor. So, my entire overly verbose tirade was meant to say - you don't need to mark non-conversion constructors (like the one with the two arguments) as explicit. They can't be used in conversion anyway.

And one last thing. I imagine that the assignment from one register to another should use the current value of the source register and assign it to the next value of the destination register. I mean, I think that it will be more consistent that way.

I did leave the code from your previous post without modifications and compiled it this way. And it appears ok. Does the compiler bark at you, or the program crashes at run-time?

Regards

P.S. If you have conversion constructor that takes int and constructs Register, than the Register to Register assignment can be used for assigning int-s also (; without separate implementation). But this is very dangerous technique, because the integer will thus overwrite all fields of the target Register (, because it gets converted to complete Register first), and consequently you will loose the ability of modifying only the next state separately, as you do now.
Hi, Simeonz -

Thanks for the reply. A few responses:

1. I still believe I do want to do implicit int-reg conversions, along the lines of what I posted above:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class reg
{
    int c, n;
public:
.
.
.
}

reg r;
int i;

i = r; // should basically do an i = r.c;
r = i; // should basically do an r.n = i; 


I can't see the downside to this behavior. But, if I have to use gets and sets instead, it's not that big a deal.

2. please disregard my comment about the default constructor failing. Cockpit error.
No. I know you want int-like behavior for Register, and I agree now that this will improve the readability. Here is what I meant by implicit conversion:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>

class Register {
private:
  int qCurrent;
  int qNext;
  int getValue() const { return qCurrent; };

  Register& setValue(int value)
  {
    qNext = value;
    return *this;
  }

public:
  //explicit
  Register(int cn = 0) : qCurrent(cn), qNext(cn) {}

  Register& operator= (const Register& reg)
  {
    qNext = reg.qCurrent;
    return *this;
  }

  operator int() const { return getValue(); }
//  Register& operator= (int value) { return setValue(value); }


  void print();
};

void Register::print()
{
  std::cout << std::hex << "qCurrent: " << qCurrent << ". qNext: " << qNext << std::endl;
}

void f(Register arg)
{
  arg.print();
}

int main(void)
{
  Register x;
  x.print(); //qCurrent: 0. qNext: 0

  x = 1;
  x.print(); //qCurrent: 0. qNext: 1

  f(2); //qCurrent: 2. qNext: 2
}
First, I have underlined a change to your assignment operator's semantics. This is one of the things that I wanted to inquire in my previous post. I don't know if it's right, but it feels right. Second, I got the compound assignments out, because they are irrelevant for the demonstration.

This is what happens when you don't use the explicit keyword to mark the constructor. Despite that there is no implementation for assignment from int to Register in the above code (as I have commented it out), all runs fine. This is because int-s are converted to Register-s from the Register(int) constructor. In other words, the line x = 1; gets translated behind the scenes from the compiler into the line x = Register(1);. Your Register to Register assignment simply transfers the next state from the source and it is produced from the int.

This does save you from implementing the int to Register assignment. It may also save you from implementing the compound assignments (if you want them), if you implement compound Register to Register assignments instead.

Here is the warning though. If you opt for that route (which I prohibited in my previous posts by using the explicit keyword) and also change the logic of the Register to Register assignment in the future, so that it does not behave like an int assignment (as it does currently with my modifications in the above example), you have to remember to turn back to separate implementation of the int to Register and Register to Register assignments. Second, if you look at function f, you can see how implicit conversion from int to Register works and decide if you want those to be allowed implicitly. In general, it is a tad bit dangerous.

This has nothing to do with whether you allow the Register to behave like an int. The question is, whether you will allow int-s to be converted implicitly to Register-s. And save yourself the trouble of implementing several assignment operators at the cost of some possible vulnerability.

Again, as I told you in my previous post, the explicit keyword does nothing for you with constructors with two or more mandatory arguments. It doesn't do anything then, literally, so you could remove it from your code. You just don't have conversion constructors. But I wanted to show you how such constructors can help you, and hurt you, and to explain to you that you don't need the keyword with the two-argument constructor in your case.

Regard
I just discovered that I hadn't tested the addition of two SocReg (as I'm now calling them) objects into a third Reg. I just tried this:

1
2
3
4
5
SocReg a, b, c;
.
.
.
c = a + b;


And got this error:

../simulatorGUI/src/DemodCicTuner.cpp:39: error: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second:
../simulatorGUI/headers/SocReg.h:32: note: candidate 1: SocReg SocReg::operator+(const SocReg&) const
../simulatorGUI/headers/SocReg.h:34: note: candidate 2: SocReg operator+(int, SocReg&)


Here are my override declarations:

1
2
3
        SocReg	operator+ (const SocReg &r) const;
        friend  SocReg operator+(SocReg &r, int i);
        friend  SocReg operator+(int i, SocReg &r);


What did I do wrong? How could the compiler consider these ambiguous?

Thanks.
That's what happens when you have too many conversion operations.
I haven't been following this thread so I don't really know, but you seem to be using operator int(). Due to this if you have SocRegs a and b, you could use the SocReg+SocReg operator or convert a to int and use int+SocReg operator.
It makes little sense to use more conversions than less (Actually I was sure that C++ wouldn't do that.. Oh well. Either our compilers are different or I'm imagining things). Anyway, C++ is apparently very sensitive about tis.

Possible solutions would be to omit operator int() or remove the two overloads of + (The ones with ints. They can probably be defined as creating a SocReg from the int and doing normal +. If so you can leave converting int to SocReg to the constructor and only define operator SocReg+SocReg. I'm not sure, but you may have to take the operator + outside the class then).

I suppose removing int() would be better as it would prevent similar errors in the future. I'm not even sure if removing +es would really solve anything..
You're 100% right, hamsterman...I did add the cast earlier, and apparently that's what tripped me up.

I'm really not sure what the better route is. Early in this thread, people advised me not to try to overuse the overloads, but when I explained what I was trying to do, I think at least a couple of people changed their minds. Now, though, the overloads for the class are getting pretty complex for my level of expertise, and I'm starting to question my decision myself.

I was just hoping to build a class that the users could treat as a simple integer, but evidently doing this is a bit more complex than I first realized. Not sure where to go from here...if I remove the int(), then I'm stuck using getters in various places.

Simeonz: on the chance that you're still reading this, I tried implementing your suggestion from above. The compiler objected to a statement like this:

regI &= BITMASK_0_13; // trim off any leading bits.

I don't know what's "special" about this particular operator that would cause a problem.

If I can solve this problem (and anything similar that crops up), I do think this is very much the preferred way to go, and I'd like to use it.

Thanks.
Topic archived. No new replies allowed.
Pages: 12