Requesting peer review of 3D Vector class.

Started by
53 comments, last by jpetrie 15 years, 9 months ago
Quote:
The thing is, though, when the Rational class is templated, the compiler can no longer figure out how to convert EITHER argument because it can't figure out what type to make the second operand. If it's defined in the class, it at least knows what type, and if it's a friend, you can do int * Rational again.

Friendship has nothing to do with what you appear to be describing. Either way, this argument isn't really a good counterpoint to Meyers' suggestion. It's failing not because of a problem with the fact that the operator is free, but because the class is ambiguous -- it has implicit conversions.

Implicit conversions (in the form of cast operators, or single-argument constructors not declared explicit) are generally considered bad, or at least dangerous, practice, because the nature of nearly all languages causes such conversions to produce an alarming number of ambiguities in name resolution. The end result is that one originally believed to be an ease-of-use optimization results in clutter and headache because of the different ways ambiguities can (or cannot) be resolved in different scenarios.

It seems like you're saying "let's not give a soldier a gun because he can shoot himself instead of the bad guys."

Quote:
The reason I can't use this for the specific is there's no way I can think of to make it so my constructor can logically accept only one variable, thus, the compiler wouldn't know how to do the conversions.

Your explanation is somewhat vague, but it strikes me that the 'explicit' keyword is what you're looking for. It prevents a single-argument constructor from being used as a conversion unless you explicit call it, e.g., T(value).

Advertisement
Quote:Original post by jpetrie
Quote:
The thing is, though, when the Rational class is templated, the compiler can no longer figure out how to convert EITHER argument because it can't figure out what type to make the second operand. If it's defined in the class, it at least knows what type, and if it's a friend, you can do int * Rational again.

Friendship has nothing to do with what you appear to be describing. Either way, this argument isn't really a good counterpoint to Meyers' suggestion.


I'm not trying to counter-point it, which is why it might have seemed weak. I really liked it! It has to be defined in the class so the compiler knows what type to convert the second operand to and it has to be friend so it can convert the first operand to the second's type (because the first operand can never convert if the function is defined in the class and the friend operator makes it so the compiler treats it as if it was not).

And, after looking back at my code, I realized that I could still benefit from putting my operators in the class (friends, of coarse) so the compiler will know what to convert things.

And explicit is not the keyword I need, but I don't feel like explaining better because the point is moot.

Also, implicit type conversions aren't bad as long as you understand them. If you know how it'll make your code act, you can prepare. They wouldn't be in the language if they weren't very helpful.
Quote:
They wouldn't be in the language if they weren't very helpful.

Right, like the auto keyword? [lol]

Quote:
It has to be defined in the class so the compiler knows what type to convert the second operand to and it has to be friend so it can convert the first operand to the second's type (because the first operand can never convert if the function is defined in the class and the friend operator makes it so the compiler treats it as if it was not).

Again, your explaination is very poor. I have no idea what you are talking about. A two-argument free function Foo(T a, T b) where T has an implicit conversion from U cannot be disambiguated by friendship. From what you've said, you're talking about making a member function versus a free function (both of which can be declared in the scope of the class).
Quote:Original post by jpetrieFrom what you've said, you're talking about making a member function versus a free function (both of which can be declared in the scope of the class).


The difference between making a free function, and a non-member, friend function.

I guess if this doesn't explain it, I'll give up:
For the the function a * b where a and b are Rational<T>, if it is defined in the class like so: Rational<T>::op*( Rational<T> b ), the compiler knows what T is because it knows the type of the first operand, a, which called the function. If b is not the right type, but easily convertible, it's fine.

A more clear way to write it:
a.operator*( 4.5f ); // 4.5f is a float, but because the constructor accepts one float, it's implicitly converted to a Rational<T>

But, a can never be converted to a Rational if it's not already. The book says that this (the keyword) can never be converted. So 4.5f.operator*( a ) is invalid because it doesn't know how to convert 4.5 to a rational, or the rational to 4.5's type. Defining it outside the class makes it so the first operand CAN be easily converted.

operator*( a, b );

But, the problem is, the compiler can't tell what T is if a and b aren't the same type.

operator*<T>( a, b );

Now if wither a or b are the wrong type, either can be converted--the book called this mixed math--but the whole point of overloading the * operator was to not have to do such clunky, confusing code. Defining it IN the class, but as a FRIEND gives the best of both worlds.

Here's the class he wrote out (minus some self-referencing comments you wouldn't understand without a copy on hand):

template<typename T>class Rational {public:  ...friend const Rational operator*(const Rational& lhs, const Rational& rhs){  return Rational(lhs.numerator() * rhs.numerator(),                         lhs.denominator() * rhs.denominator());  }                                                         };


So now if a (lhs) OR b (rhs) is of type T, an implicit conversion will take care of the complication. The compiler looks in the class for an operator, and it finds this. Otherwise it looks here and the float class and finds nothing. It knows what type T is because it looks in the class.

This solved a conversion problem I had with magnitude( Vector<T>& v, T newMag ) where if the new mag wasn't of type T, it didn't work.
Quote:
...things...

Right, what I've been saying is not that you're wrong, it's that your explanations and terminology do not jive such that its easy to interpret your words as saying something that isn't true or doesn't make sense.

This topic is closed to new replies.

Advertisement