Click here to Skip to main content
15,881,424 members
Please Sign up or sign in to vote.
4.45/5 (4 votes)
See more:
I've wrapped the int class, with a new type: Integer

(I've got most of my operators set up to work just like the original int type does.)

C++
Integer i; // Integer i(100); also works.
i = 100;


I want to be able to do this.

C++
Integer i(100);
int temp = i;


So, is there an operator I can override to accomplish this, it would seem like there is. (I thought it was the "operator=", but, that didn't seem to work, it was still treated as my custom type, and complained about needing a cast to int to work.)

In the end, I just want my wrapped type to work like the original, so that it can be used with any function the original could, and in the same way.

C++
void Add(int a, int b) {.....}

Integer a(100);
Integer b(200);

Add(a, b);


Here is my class implementation.

C++
#pragma once
#include "Object.h"

// Declaration: Integer
class Integer : public Object<int>
{
	public:
		Integer();
		Integer(int source);
		int operator=(int source);
		int operator+(int source);
		int operator+=(int source);
};

// Constructor
Integer::Integer()
{
	Integer::Value = 0;
}

// Constructor
Integer::Integer(int source)
{
	Integer::Value = source;
}

// Operator: Assignment
int Integer::operator=(int source)
{
	Integer::Value = source;
	return Integer::Value;
}

// Operator: Compound Assignment
int Integer::operator+=(int source)
{
	Integer::Value = Integer::Value + source;
	return Integer::Value;
}

// Operator: Addition
int Integer::operator+(int source)
{
	return Integer::Value + source;
}
Posted
Updated 31-Jan-12 6:19am
v2
Comments
Andreas Gieriet 31-Jan-12 18:06pm    
Hello Joe_Dert,

I hope that this is an academic exercise only, for the excitment to learn something ;-) Reinventing the wheel is very costly, especially if one wants to mimic the behaviour of something existing, like a basic type.

One lesson I did learn is: if you have a class where you are tempted to overload an arithmetic operator, you are likely to reinvent the wheel in some or the other way. The same applies for implementing any kind of string class.

E.g. if you want to do "+" a concatenation of some objects, make a concat() function. The client of your library and your fellow maintainer will thank you!

The abolutely worst thing is *implicit* conversion (as suggested further down). It opens up the box of pandora - "magic" conversion with undesired results will be a nightmere to analyze and fix, especially once the project grows to a decent size...

In a professional project, you will not find any re-invented numeric types or any re-invented string classes (unless one is ignorant about development efforts...).
Maybe these thoughts are worth to consider...

Cheers

Andi
Philippe Mori 31-Jan-12 22:42pm    
In such case, I think you definitively do not want to define operators like +.

Also, that solution won't easily be expandable to support more types (and in a type-safe manner). For example, if you have a double and want to print it, how will you do? If you have to explicitly tell the target type (say Double instead of Integer).

With templates and polymorphic classes, it would be possible to have a system that would works but it would be complex to built and not be very efficient.

In your case, I think that it would be far better to uses an approche similar to iostream or STL algorithm.

In pratice, it would be preferable to have the following: std::string to_string(int a) { /* implementation here */ } as it is easy to extend that for other types and the compiler will automatically call the appropriate function.

Put those utilities functions in there own namespace then you can selectively use them...
Emilio Garavaglia 1-Feb-12 3:07am    
Andi, please... Stop coding C++ and use just only Java.

Having a type other then integer that is subject to integer arithmetic is normal if you need a numeric type that doesn't intermix with the other and can overload differently.

About + for concat(), do you know that std::string use + just forn that? If you think STL is somehow stupid, lieave away C++. Otherwist try thinkibng differently.
C++ programmers know operators are overridable, and know c=a+b is a generic expression that is not necessarily the assignment of the result of an arithmetic addiction. Please open your mind (and mindset!)
Your concept of "professional large project" is based on C++99 state of art.
Andreas Gieriet 1-Feb-12 4:29am    
Thank you for your kind advise ;-)

Please read my comment carefully: It's all about about classes you write yourself.

STL et. al. are very well designed in a meaningful way. It makes sense to have the "+" operator to concatenate these *standard* strings. I'm not challenging that.

I'm challenging the definition of arithmetic operators in *client* classes and especially implicit casts. That's all.

I'm glad that the spirit of C/C++ is "to enable things". This comes with the price to "restrict yourself" to some degree. This "restrict yourself" is what I suggest in my comment above.

Troubles with self-invented numeric types are:

- how the compier deduces argument match with overloaded functions


1. exact match

2. promotion


3. coercion


4. implicit conversion (implicit constructor call)

I'm not even sure if I got it right from memory...

- you can not tell the compiler not to do so.

My advise: Leave that trouble to STL and 3rd party numeric library provides and spend time on your core busines instead.

Cheers

Andi

PS: I don't get your last statement. What do you mean by that?
Emilio Garavaglia 1-Feb-12 4:43am    
The only thing I understand is a "racism" between my classes and the standard ones.

But both relies on the same core language using the same core features.
And both can be "well designed" (as both can be criticized).

The standard library itself was all but "standard" before the standardization process took place.

At that time was as set of classes written by someone. But since the where classes someone wrote himself (according to your concept) shouldn't be designed that way ...
If I have something that behave as a string, I have the full right to concatenate the same way. I'm just re-using a "well known pattern".

You advise (knowing nothing about who I am, or the OP is) is tendencious: "Leave that trouble to STL and 3rd party numeric library provides and spend time on your core busines instead". My be my business is writing those libraries!

Also consider the "learning curve": if writing a class (considering also the time to test it) takes less than learning how to use a 3rd partly library, where is the trade-off? The answer is all about simple and trivial!

Use const operator overloading.

C++
operator int()    const { return Value; }
operator double() const { return (double) Value; }
 
Share this answer
 
Comments
[no name] 31-Jan-12 13:14pm    
Very nice, works just as expected. :)

Thank you. :D
Philippe Mori 31-Jan-12 20:13pm    
Although it seems to works... it is really a bad idea to have conversion operator in such case as it hard to predict the type of expression and when predefined operator are used and when user defined operator are used.

For example, if you do Integer a(1); auto b = a = 1; what is the type of auto? You probably expect Integer but with your code, you would get int. It only get worst when other operators and types are used.

The best solution is to force the use of anh explicit function to get the internal value as an int.
[no name] 31-Jan-12 21:44pm    
I'm well aware of the behavior, its intended, I NEVER want Integer back, I put in int, and get back int.

It's to be treated just like an int, I thought I mentioned that in my OP.

Integer is a container type for ints now, it's based off my abstract Object class, which makes this easy to manage, and extend.

So, while this may generally be a bad idea, I think, it's okay in this case.

But, the whole point of doing this was because the syntax allowed it(operator overloading, etc,.), and I wanted to really push it, and make my own little mini language.
Philippe Mori 31-Jan-12 22:18pm    
Then why the trouble to define all those operator? Just define the conversion operator and it will almost always be used as an int.

By the way, since operators like + are not symmetric, if a is an int and b an Integer, then A + b would use predefined + while b + a would use your operator.
[no name] 31-Jan-12 22:52pm    
Because, Integer has friends. (Double, and Float.)

Integer i(100);
Double d(i);

Is valid, and works as expected, I need nothing more from it, I can go between custom types, and default types. (In\Out, like a data stream..)

I'm exploiting operator overloading to create a custom syntax, I'm not trying to properly implement operator overloading.

I understand what I've done.
Looks like I'm late to the party, since, apparently, at the very least your original problem was solved, and there also seems to be plenty of advice in that context and in fact a wide scope around it.

What I was missing though after skimming over the various responses (but I may have skipped it), was a word of advice on the declaration and use of arithmetic operators. Others already pointed out the danger of implicit conversion operators, but what I think they failed to mention is that arithmetic operators, too, can cause unwanted conversions.

Consider the following:
C++
class Integer {
   int Value;
public:
   Integer(int v=0) : Value(v) {}
   Integer(const Integer&  v) : Value(v.Value) {}
   Integer& operator=(const Integer&  v) { Value = v.Value; return *this; }
   int operator+=(const Integer& v) { Value += v.Value; return Value; }
   friend int operator+(const Integer& v1, const Integer& v2);
   friend int operator-(const Integer& v1, const Integer& v2);
   friend int operator*(const Integer& v1, const Integer& v2);
   friend int operator/(const Integer& v1, const Integer& v2);
};
void foo() {
   Integer a(3), b(5), c(7), d(11);
   Integer e = (a+b)*(c-d); // (1)
   Integer f = (b+=13)/(c+=d); // (2)
}

(1) this line will first invoke int operator+(const Integer& v1, const Integer& v2) and int operator-(const Integer& v1, const Integer& v2), and then construct e using Integer(int v). Since the result of the first two operators are both int, the multiplication will not invoke int operator*(const Integer& v1, const Integer& v2).

(2) Similarly, this line will invoke Integer(int v=0) to convert 13 to Integer, then invoke int operator+=(const Integer& v) twice, and finally use Integer(int v=0) to construct f. Again, the result type of operator+=(...) prevents the invocation of int operator/(const Integer& v1, const Integer& v2).

Now, if you want to implement some behaviour in Integer that is different from int (and there would be no point if that wasn't your intention), then you may want to control the results of multiplications and divisions just as you do additions and substractions. But, by declaring the result types as int, you implicitely convert intermediate results of your arithmetic operations to int, preventing the use of your own operators.

What you have to do instead is return objects or references to your class, for any operator where it makes sense. You can always add an explicit conversion back to int when you need it - but not before:
C++
class Integer {
   int Value;
public:
   Integer(int v=0) : Value(v) {}
   Integer(const Integer&  v) : Value(v.Value) {}
   explicit operator int() const { return Value; } // (4)
   Integer& operator=(const Integer&  v) { Value = v.Value; return *this; }
   Integer& operator+=(const Integer& v) { Value += v.Value; return *this; }
   friend Integer operator+(const Integer& v1, const Integer& v2);
   friend Integer operator-(const Integer& v1, const Integer& v2);
   friend Integer operator*(const Integer& v1, const Integer& v2);
   friend Integer operator/(const Integer& v1, const Integer& v2);
};
void foo() {
   Integer a(3), b(5), c(7), d(11);
   Integer e = (a+b)*(c-d); // (1)
   Integer f = (b+=13)/(c+=d); // (2)
   int g = (int)f; // (3)
}


Now your operator*(...) and operator/(...) will be called, since the intermediate results are of type Integer.

Note that the result type of operator+=(...) is a reference! This saves you (or the runtime system) the effort of creating a temporary that you otherwise would need as an argument for operator/(...)

(3) Also note that I added the keyword explicit to the conversion operator to prevent unwanted implicit conversion. Now, if you want to convert an Integer to an int, you have to explicitely cast it. That should serve your purpose, but is a cleaner and safer way to do it.

(4) A belated note regarding my suggested use of explicit (thanks to Andreas Gieriet for pointing this out):
Using the keyword explicit on conversion operators is a new feature of C++11. Not all compilers support it (certainly not the older versions).
 
Share this answer
 
v4
Comments
Andreas Gieriet 1-Feb-12 5:30am    
Nice an comprehensive descrition. My 5!
Stefan_Lang 1-Feb-12 6:00am    
Thank you. Although this goes beyond the scope of the original question, I've thought it prudent to point out why operator return types should stay 'in class' - if not for the authors sake, then at least for the sake of others searching the site on the topic of C++ operators.
Andreas Gieriet 1-Feb-12 6:52am    
Hello Stefan,

I just noticed that this code only compiles with the latest C++11 compiler ("explicit" for the cast operator was only introduced in C++11).

The class would be consistent if both, the cast operator as well as the conversion constructor are defined as "explicit" since both get involved in type deduction if they are implicit. But I guess, then the usage was a bit annoying, since on could not call Integer b(5); b+=10;...

Cheers

Andi
Stefan_Lang 1-Feb-12 7:19am    
Thanks for the info, I wasn't aware of that. I'll update the solution to point this out.

I do agree that making the constructor explicit as well would be awkward. That is something you should do only when there is a danger of unwanted conversion. E. g. when you define a class Vector with a constructor that has just one integral argument, meant to denote the size of the coefficient array, then you should make that constructor explicit to avoid unwanted conversions from statements such as:

Vector v = 5; // attention: this would call Vector::Vector(int)
Andreas Gieriet 1-Feb-12 7:32am    
I did not try out, but got that info from a very goob book about the topic (only available in German): C++11: Der Leitfaden für Programmierer zum neuen Standard.
This book is dedicated to the changes from C++98 and covers how the available comiles deal with it.
Although that approach might works (even though you still have to learn a lot before having something robust enough for commercial applications), I would not recommand that route.

Something like that would be more in the philosophy of C++:

C++
namespace utilities
{
  std::string to_string(int a) { /* ... */ }
  std::string to_string(double a) { /* ... */ }
}

using namespace utilities;
int a = 1;
double b = 1.1;

// No explicit use of a type like Integer and select appropriate code at compile-time
to_string(a);  
to_string(b);


If you absolutly need polymorphism (I assume that your Object template class has a common class with methods like ToString()), then, you would need a much more complex design... particulary if you want to achieve a behavior similar to C#.
 
Share this answer
 
Comments
[no name] 1-Feb-12 0:04am    
Perhaps, but, remember, I'm picking and choosing features from each language, and trying to emulate them as simply as possible.

I took what I needed, made it work, and moved on.

I'm not getting too tied down to C#'s object model, just mimicking it here for standard ToString, GetType, etc,.

There are plenty of ways I could have done this, but, I wanted specific syntax, and usage, and I've wanted to mess with operator overloading, so, here we are.

Btw, ToString is part of Object, it's virtual, Object is an abstract class, just override ToString() where needed, otherwise, the T version in Object works for most types, as seen in Integer.

(Notice that I call Integer.ToString() in my examples, but, it's not defined in Integer, the object model IS that simple, all types derive from Object, Object exposes ToString, which get's overridden in its inheriting classes as needed, for special formatting, ie, [X: 0 Y: 2000], etc,.)
Stefan_Lang 1-Feb-12 6:07am    
I haven't read all your comments, but from this response I get the very strong impression that your only goal is to mimick C# behaviour, ignoring all the bad side efects this has on performance and memory consumption. If performance and memory is not an issue, why do you use C++ at all? Why not just go with C#?
Stefan_Lang 1-Feb-12 6:05am    
I agree that in this case your suggestion makes much more sense. All that the Integer class really achieves is eat memory (for using a derived class with virtual functions) and performance. Your suggestion does neither.
[no name] 13-Feb-12 17:11pm    
Personally, I would rate this solution down, you've had no problem bashing my code, so, let's dissect your solution, and why it's not good.

We'll ignore the fact that this won't even work for most ppl, since many compilers don't support it yet. (It's part of the C++ 11 standard.)

We'll ignore that it's such basic info, it wasn't even worth posting. (Are you seriously telling me how to use namespaces\methods? /face palm)

Down to business, so, what are you suggesting?

That I override an existing standard type, by hiding a needlessly complex re-implementation of itself inside a new namespace? No thanks.

You'd be much better off either using std::to_string as intended(assuming it works logically, I haven't actually used it.), or writing your own implementation.

So assuming one had to write their own,
they'd be better off doing it with a template.

template class T
std::string ToString(const T& source)
{
// You need to discover the type here, then, you need to supply the proper conversion methods for each type, via a switch\if statement.

// Then, I would use stringstream or similar to convert the type to a properly formatted stringified version of itself.

// Of course, you need to catch unknown types, and return some sort of error, etc,.

return result;
}

I didn't bother properly typing out the solution, because the forum would mangle it anyways, but, even this isn't a good solution. (Though, it's better than yours, for too many reasons to explain here.)

One major flaw, is that it's not maintainable, this works fine for one type, or two, or three, etc,.

But, what about thousands of types? Would you want to maintain your Utilities namespace with thousands of conversions? (My OP is about a modification to C++ itself, and thus needs to support millions of users, and user types, your method can't handle it.)

Even my suggested method(in this post) requires you to update the ToString method to add new types, it won't scale well. (At all, actually.)

The object model solution, embeds ToString into every object you create, and is overridable per class, thus, you don't have to maintain some mess of conversions, each author can maintain their own conversion, and keep it hidden in their class implementations.

It's much more manageable on a large scale with the object model in place. (Not to mention, it enforces a standard where every object uses the same method for conversion to a string.)

So, in the future, don't disrespect my code if you can't even understand the full implications of the subject matter, your solution is childish, and fails to address many complex issues.
Philippe Mori 13-Feb-12 20:00pm    
My code does not requires C++ 11. And if can be implemented in a way that would as maintainable as your solution. One useful trick would be to forward to your root Object class for any class derived from object: std::string to_string(const Object &o) { return o.ToString(); }. Using that approche, you only have to defines functions for builtin type or code you cannot modify.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900