Department of Engineering

IT Services

C++ type anomalies and special cases

C++'s type discipline (the way it treats different variable types differently) can sometimes produce situations where the behaviour might look odd.

References

Try compiling this

#include <iostream>
using namespace std;
int main ()
{
  int i = 1;
  int& ref1 = i;
  const unsigned& ref2 = i;

  i = 2;
  cout << "i=" << i << ", ref1=" << ref1 << ", and ref2=" << ref2 << endl; 
}

Note that it compiles ok, mostly likely without a warning. Before running it, guess what it will print out. ref1 behaves as expected - the types match - but ref2 is more problematic. If ref2 is going to be an alias for i it needs to be the same type. Without the const it wouldn't compile, but here the compiler creates a temporary "unsigned int" object with the current value of i and makes ref2 an alias for it, so subsequent changes to i will only affect ref1, not ref2 (which after all is supposed to be a constant).

unsigned values

Alf P. Steinbach's page shows a few of the issues - not type anomalies as such, but potentialy unexpected. For example, this prints the integers 5 ... 0

#include <iostream>
using namespace std;

int main() {
  for (int i=5;i>=0;i--)
    cout << i << endl;
}

but this loop prints a lot more

#include <iostream>
using namespace std;

int main() {
    for (unsigned int i=5;i>=0;i--)
    cout << i << endl;
}

conversion constructors

There are several was to create and initialise variables. The next piece of code shows a few ways to create an integer with the value 8.

#include <iostream>
using namespace std;
int main ()
{
  int i1 = int(8.8);
  int i2(8.8); 
  int i3;
  i3 = 8.8;
}

As you can see, initialisation may involve situations where type conversion is necessary. Because the code above uses fundamental types between which conversion is defined, there's no problem. With user-defined types however, there are potential complications because conversions need to be defined.

#include <iostream>
using namespace std;
  class A {
  public: 
    int val;
    A(int i) {val=i;};
  };

int main ()
{

  A a1=8.8;
  A a2= A(8.8);
  A a3(8.8);

  cout << a1.val << " " << a2.val << " "<< a3.val << endl;
}

Note that even "A a1=8.8;" calls the user-defined constructor. This is an example of a converting constructor - when there's a single constructor taking one suitable argument it's used to convert from the type of the parameter (or implicitly from the type of the RHS) to the type of the class. The code compiles ok, but you might sometimes wish it wouldn't. If you change the contructor line to

explicit A(int i) {val=i;};

and try recompiling you'll find that "A a1=8.8;" is no longer legal. The constructor now has to be called explicitly.

It's also possible to assign a variable of a user-defined type to another type. The code below uses a "conversion operator" (or "cast operator") so that an object of type A can be assigned to an integer

#include <iostream>
using namespace std;

class A {
  public: 
    int val;
    A(int i) {val=i;};
    operator int() const { return val; };  //  cast operator  
};

int main ()
{
  A a1=8.8;
  int x=a1;
  cout << "x=" << x  << endl;
}