Built in type inheritance.

A

Alan Johnson

For various reasons I'd like to be able to inherit from the built in
types, which obviously you cannot do. To get around this, I made a
template to emulate the built in types. So far I can't find any
situations in which this template fails to work interchangably with the
built in types. My question is if anyone knows of any gotcha type
situations in which this is going to fail.

Thanks,
Alan

template <typename T>
class built_in
{
private :
T value ;
public :

built_in() {}
built_in(const T &t) : value(t) {}

operator T() const { return value ; }
operator T&() { return value ; }

T &operator=(const T &t) { return (value = t) ; }
T &operator %=(const T &t) { return value %= t ; }
T &operator &=(const T &t) { return value &= t ; }
T &operator |=(const T &t) { return value |= t ; }
T &operator ^=(const T &t) { return value ^= t ; }
T &operator +=(const T &t) { return value += t ; }
T &operator -=(const T &t) { return value -= t ; }
T &operator *=(const T &t) { return value *= t ; }
T &operator /=(const T &t) { return value /= t ; }
T &operator <<=(const T &t) { return value <<= t ; }
T &operator >>=(const T &t) { return value >>= t ; }
T* operator&() { return &value } ;
} ;
 
J

John Carson

Alan Johnson said:
For various reasons I'd like to be able to inherit from the built in
types, which obviously you cannot do. To get around this, I made a
template to emulate the built in types. So far I can't find any
situations in which this template fails to work interchangably with
the built in types. My question is if anyone knows of any gotcha type
situations in which this is going to fail.

Thanks,
Alan

template <typename T>
class built_in
{
private :
T value ;
public :

built_in() {}
built_in(const T &t) : value(t) {}

operator T() const { return value ; }
operator T&() { return value ; }

T &operator=(const T &t) { return (value = t) ; }
T &operator %=(const T &t) { return value %= t ; }
T &operator &=(const T &t) { return value &= t ; }
T &operator |=(const T &t) { return value |= t ; }
T &operator ^=(const T &t) { return value ^= t ; }
T &operator +=(const T &t) { return value += t ; }
T &operator -=(const T &t) { return value -= t ; }
T &operator *=(const T &t) { return value *= t ; }
T &operator /=(const T &t) { return value /= t ; }
T &operator <<=(const T &t) { return value <<= t ; }
T &operator >>=(const T &t) { return value >>= t ; }
T* operator&() { return &value } ;
} ;

You are missing a semi-colon in your final operator.

The fact that you need a conversion operator in order to get the value will
prevent the compiler from making other conversions that it would make if the
built in type were used. Consider the following:

// define your class template here


#include <iostream>
class Test
{
public:
Test(int x) : x_(x)
{}
void Print() const
{
std::cout << x_ << std::endl;
}
private:
int x_;
};

void foo(const Test & test)
{
test.Print();
}

int main()
{
built_in<int> n(3);
foo(3); // compiles since Test constructor with int parameter is called
foo(n); // won't compile since compiler will not do two conversions ---
// from built_in<int> to int and then from int to Test.
}
 
J

John Carson

int main()
{
built_in<int> n(3);
foo(3); // compiles since Test constructor with int parameter is called
foo(n); // won't compile since compiler will not do two conversions ---
// from built_in<int> to int and then from int to Test.
}


You can work around this with a cast:

foo((Test)n);

but some of the convenience is lost.
 
H

Heinz Ozwirk

Alan Johnson said:
template <typename T>
class built_in
{ [...]
T* operator&() { return &value } ;
} ;

That will make it difficult to get the address of an instance of some built_in<T>. You should add a member function to get an object's address, like

// inside class built_in
built_in* GetAddress() { return this; }
built_in const* GetAddress() const { return this; }

Or better (IMHO) remove operator&() and add a member function to get the address of the value member, like

T* Pointer() { return &value; }
T const* Pointe() const [ return &value; }

Regards
Heinz
 
J

Jonathan Mcdougall

For various reasons I'd like to be able to inherit from the built in
types, which obviously you cannot do.

I'm curious to know what these various reasons are...
 
A

Alan Johnson

Jonathan said:
I'm curious to know what these various reasons are...

Two reasons come to mind immediately. First, I'd like to be able to add
arbitrary elements to the domain of a type. Just an off the top of my
head example, some shortest path algorithms rely on dynamically updating
the distance between two graph nodes with the best distance found so
far. To initialize such an algorithm, you set the current best distance
between two points at infinity. Because the 'int' type doesn't have a
representation for infinity, one possible workaround would be to create
a type that inherits from int (were that possible) and add a flag to
indicate infinity. However, you'd like for that type to behave like an
int in all cases except when testing it against infinity.

Another reason would be to make semantically distinct types. For
example, I might write a program that uses the unsigned type for both
the purpose of storing the dimensions of an image, and also for
providing indices into an array. Under normal circumstances these
should never be interchangable. Dimensions of an image have nothing to
do with array indices (in our theoretical example) and any assignment
between the two is a runtime error. It would be nice if we could
inherit from int to make two distinct types that, while both behaving
like integers, were distinct as far as the type checking system was
concerned. Then any assignment between the two would become a compile
time error, which is far preferable.

For a better example of types that differ only in semantics, I recommend
Joel Spolsky's article: http://www.joelonsoftware.com/articles/Wrong.html

He recommends using a naming convention to keep encoded and unencoded
strings separate. I think a more robust system would be to create
different types, that both behaved like (inherited from) strings, but
could not be used interchangably with each other.

-Alan
 
J

John Carson

Alan Johnson said:
Two reasons come to mind immediately. First, I'd like to be able to
add arbitrary elements to the domain of a type. Just an off the top
of my head example, some shortest path algorithms rely on dynamically
updating the distance between two graph nodes with the best distance
found so far. To initialize such an algorithm, you set the current
best distance between two points at infinity. Because the 'int' type
doesn't have a representation for infinity, one possible workaround
would be to create a type that inherits from int (were that possible)
and add a flag to indicate infinity. However, you'd like for that
type to behave like an int in all cases except when testing it
against infinity.

Or you could initialise the value of the int to

std::numeric_limits<unsigned int>::max()

(declared in the limits header). If this isn't as large as your shortest
path, you have a problem anyway.
Another reason would be to make semantically distinct types. For
example, I might write a program that uses the unsigned type for both
the purpose of storing the dimensions of an image, and also for
providing indices into an array. Under normal circumstances these
should never be interchangable. Dimensions of an image have nothing
to do with array indices (in our theoretical example) and any
assignment between the two is a runtime error. It would be nice if
we could inherit from int to make two distinct types that, while both
behaving like integers, were distinct as far as the type checking
system was concerned. Then any assignment between the two would
become a compile time error, which is far preferable.

There is a suggestion before the standards committee to create an extended
form of typedef that would create a new type (as opposed to merely an alias
for a type, which is what the current typedef does). This would be an
alternative and simpler way to get the type safety you seek.

I don't mean to dispute that having built-in types as fully fledged classes
would have some advantages (another one that springs to mind is gaining full
control over initialisation).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,294
Messages
2,571,518
Members
48,226
Latest member
AngusMays0

Latest Threads

Top