Vectors wih usual arithmetic conversions

Started by
7 comments, last by NotAYakk 17 years, 6 months ago
I'm writing a set of math primitive class templates (vectors, matrices, quaternions, and axis-angles). I've been wrestling with whether I should implement transparent support for the usual arithmetic conversions. Through templates, I can find the smallest vector (type and dimension) that can encompass two differently typed vectors in a binary arithmetic operation. Here's how it works: Vector<3, int> a; Vector<2, float> b; a + b; // yields a Vector<3, float> This allows for implicit upward conversions without any risk of accidental loss of information. Its also a bit more complex from the implementation perspective, although I already have the basic functionality written. Should I bother? Also, does it make sense to derive the Quaternion class from Vector<4>? They share some functionality, but they're also fundamentally different.
Advertisement
Quote:Original post by dcosborn
Through templates, I can find the smallest vector (type and dimension) that can encompass two differently typed vectors in a binary arithmetic operation.
[...]
This allows for implicit upward conversions[...]


I wouldn't do this.
1. I don't see any use for this.
2. With every implicit conversion you allow compiler to make, you're loosing some "type safety" - here: for every mistake in code you make, the compiler will allow it. You misspell Vector<3,float> with 2, and it will pass in silence.
Some orthodoxies go even further and separate points from vectors, to enforce this kind of "type safety" even more.

As for the implicit upward cast, it seems like an interesting concept for me, but better ask yourself - how are you going to handle downward cast (double->float, for example). Compilers usualy give warnings for those - will your code also do this?

Quote:Original post by dcosborn
Also, does it make sense to derive the Quaternion class from Vector<4>? They share some functionality, but they're also fundamentally different.


See above, point 2.
You'll write (v+q) and it will be ok for the compiler.
Thanks for the response. Actual casts (upward or downward) would be explicit. The idea is that an operation such as + would produce a vector that is fully capable of containing both input types. This is exactly what the basic arithmetic (float, int) and enum types do. So no loss in type safety beyond what the standard allows. Here's how it looks implementation wise:

template <unsigned n1, typename T1, unsigned n2, typename T2> 	inline typename VectorArithmeticConversion<n1, T1, n2, T2>::Result operator +(const Vector<n1, T1> &, const Vector<n2, T2> &);

Note that this only deals with arithmetic operators. Assignment and relational operators would take differently typed vectors, but the return type would be more typical. Assignment would return a reference to the lvalue. Relational operators could return a Vector<bool> which could be combined into a single bool using functions like Any or All. But again, with the relational operators, the underlying types, whether ints or floats, would be converted using the "usual arithmetic conversions"; it would just happen automatically and under the covers.

I'm now thinking that this ability to take different lvalue and rvalue types is currently a bad idea because its unexpected. And, as you said, its actual usefulness is questionable. And I see your point about the quaternion.

[Edited by - dcosborn on October 16, 2006 5:29:59 PM]
Adding a two-dimensional vector to a three dimensional vector doesn't really make sense. I personally would not expect it to be possible without explicitly specifying how the conversion should take place. V2d is a plane...but where?

As for type conversions...if you can, great. float + int makes logical sense, so why wouldn't vector<3, float> + vector<3, int>?

CM
Personally, I find a hell of alot of bugs are caused by implicit conversion between floats and ints.

Heck, the standard conversion from a float to an int is insane -- truncation?! Do you really want to know how many visual glitches that causes in production code?

And what the "correct" thing to do is -- do you round up, down, or to the nearest? What do you do with 9.5000000?

And if you upcast -- what do you turn a 64 bit int into when added to a float?

Write quick-and-easy-to-use EXPLCIT conversion functions.

template<int n, int m, typename scalar>Vector<n, scalar> ReDim( Vector<m, scalar> input );template<  typename dest_scalar,  typename src_scalar,  typename Converter=CastConverter<dest_scalar, src_scalar>,  int n>Vector<n, dest_scalar> ReScalar( Vector<n, src_scalar );


Then
Vector<3, int> a;Vector<2, float> b;a + b; // fails to compileReScalar<float>(a) + ReDim<3>(b); // compiles, result is obvious

Thanks guys. Its good to hear your advice as it keeps me from pursuing an unproductive path. I've decided to keep it simple, implement explicit copy constructors for the different types, and require the types to be the same for all operations. I guess the cuteness factor (and contained usual arithmetic conversions are pretty cute) isn't worth the added complexity.
May I advise making the copy constructors explicit?

You really can't convert an int to a float losslessly. Lossy operations should be explicit.
Quote:Original post by NotAYakk
May I advise making the copy constructors explicit?


Quote:Original post by dcosborn
I've decided to keep it simple, implement explicit copy constructors for the different types, and require the types to be the same for all operations.


(Note: I do not condone making the honest to goodness copy constructors explicit, as this only serves to make assignment-style syntax construction invalid. Conversion constructors on the other hand, which you both seem to be talking about, I won't disagree with.
Bah, ya, I meant conversion copy constructors -- not the self-copy constructor.

A copy constructor that is cheap and lossless shouldn't be made explicit.

Note that adding implicit cast-from operators can bypass explicit conversion copy constructors. Cast-from operators are run implicitly -- so they should only exist if they are cheap and lossless.

This topic is closed to new replies.

Advertisement