Skip to main content
gamedev.net
🔒 Locked

Vector3D class, optimization advice

Started by Veslefrikk 30 replies 4.7k views
Original Post
Veslefrikk
Veslefrikk
Hi, put together a template Vector class today. This will be utilized alot in my engine so I would love to get some feedback on what can be done better/faster.

template <typename T>
class Vector3D
{
private:
	T x,y,z;
protected:
public:
	Vector3D();
	Vector3D(T,T,T);
	Vector3D(Vector3D&);
//	~Vector3D();

	bool		operator==(const Vector3D&) const;
	Vector3D<T>	operator+(const Vector3D&) const;
	Vector3D<T>	operator-(const Vector3D&) const;
	Vector3D<T>	operator-() const;
	Vector3D<T>	operator/(float) const;
	Vector3D<T>	operator*(float) const;
	Vector3D<T>	operator*(const Matrix&) const;
	void		operator+=(const Vector3D&);
	void		operator-=(const Vector3D&);

	T			Dot(const Vector3D&) const;
	Vector3D<T>	Cross(const Vector3D&) const;
	T			Magnitude() const;
	T			Distance(const Vector3D& v) const;
	void		Normalize();

	void		Set(T,T,T);
	void		Set(Vector3D&);
	void		Print() const;


	friend ostream&	operator<<(ostream& os, const Vector3D&);
};

template <typename T> Vector3D<T>::Vector3D() : x(0), y(0), z(0) {}

template <typename T> Vector3D<T>::Vector3D(T _x, T _y, T _z) : x(_x), y(_y), z(_z) {}

template <typename T> Vector3D<T>::Vector3D(Vector3D& v) : x(v.x), y(v.y), z(v.z) {}

template <typename T> bool Vector3D<T>::operator==(const Vector3D& v) const
{	return (x == v.x && y == v.y && z == v.z);	}

template <typename T> Vector3D<T> Vector3D<T>::operator+(const Vector3D& v) const
{	return Vector3D( x + v.x,  y + v.y,  z + v.z);	}

template <typename T> Vector3D<T> Vector3D<T>::operator-(const Vector3D& v) const
{	return Vector3D( x - v.x,  y - v.y,  z - v.z);	}

template <typename T> Vector3D<T> Vector3D<T>::operator-() const
{	return Vector3D( -x, -y, -z);	}

template <typename T> Vector3D<T> Vector3D<T>::operator/(float s) const
{	float sInv = 1.0f / s;
	return Vector3D( x*sInv, y*sInv, z*sInv);	}

template <typename T> Vector3D<T> Vector3D<T>::operator*(float s) const
{	return Vector3D( x*s, y*s, z*s);	}

template <typename T> Vector3D<T> Vector3D<T>::operator*(const Matrix& m) const
{
	return Vector3D(x*m.m11 + y*m.m12 + z*m.m13,
					x*m.m21 + y*m.m22 + z*m.m23,
					x*m.m31 + y*m.m32 + z*m.m33 );
}

template <typename T> void Vector3D<T>::operator+=(const Vector3D& v)
{	x+=v.x;	y+=v.y;	z+=v.z;	}

template <typename T> void Vector3D<T>::operator-=(const Vector3D& v)
{	x-=v.x;	y-=v.y;	z-=v.z;	}

template <typename T> T Vector3D<T>::Dot(const Vector3D& v) const
{	return v.x*x + v.y*y + v.z*z;	}

template <typename T> Vector3D<T> Vector3D<T>::Cross(const Vector3D& v) const
{	return Vector( y * v.z  -  z * v.y, z * v.x  -  x * v.z, x * v.y  -  y * v.x );	}

template <typename T> T Vector3D<T>::Magnitude() const
{	return sqrt( x*x + y*y + z*z );	}

template <typename T> T Vector3D<T>::Distance(const Vector3D& v) const
{	return (*this - v).Magnitude();	}

template <typename T> void Vector3D<T>::Normalize()
{
		T mag = ( x*x + y*y + z*z );
		if (mag == 0) return;
		T mul = 1/sqrtf((float)mag);            
		x *= mul;	y *= mul;	z *= mul;
		return;
}

template <typename T> void Vector3D<T>::Set(T _x, T _y, T _z)
{	x=_x; 	y=_y;	z=_z;	}

template <typename T> void Vector3D<T>::Set(Vector3D& v)
{	x=v.x;	y=v.y;	z=v.z;	}

template <typename T> void Vector3D<T>::Print() const
{	cout << fixed << "X: " << x << "\tY: " << y << "\tZ: " << z << endl;	}

ostream& operator<<(ostream& os, const Vector3D<float>& v)
{
	return os<< fixed << "X: " << v.x << "f\tY: " << v.y << "f\tZ: " << v.z << "f" << endl;
}


Stefan Edit: source formatting works, must have done something wrong the first time around. :) [Edited by - Veslefrikk on December 9, 2008 12:00:44 AM]
Zakwayda
Zakwayda
I would put the source tags back in; reading code that's entirely unformatted is a pain.

What sort of formatting problems were you having? One thing you might try is converting any tabs to spaces before pasting the code into the post (I've found that hard tabs don't always format correctly on the forum).
Cornstalks
Cornstalks
1) Add a *= (float) method

2) Return the vector after doing an assignment. Example:

Vector3D& operator+=(const Vector3D&);

template Vector3D& Vector3D::operator+=(const Vector3D& v)
{ x+=v.x; y+=v.y; z+=v.z; return *this}

3) Add a method to get the squared magnitude (so you don't have to do an expensive sqrt operation if it's not needed)

4) Add a SetLength(float) (or SetMagnitude(float))method to allow you to set the length (or magnitude). It's similar to a Normalize() and then multiplying by the float.

5) The Set(Vector3D&) function looks awkward. operator = (const Vector3D&) seems more natural.

That's all I can think of at the moment. Most of them are geared towards functionality rather than performance. I don't know if there is a whole lot more you can do for performance.
Drigovas
Drigovas
sqrt has a float and a double overload already that return the type of what you pass in. This means that there is really no point in casting to a float, performing sqrtf explicitly, and casting back, which saves you some when dealing with doubles in terms of accuracy. This will effectively limit your class to using floats and doubles though for the full set of functions. Then again, pretty much everything that flows through that section wouldn't make any sense as a type of anything other than a float or double, so it really isn't much of a loss. Just don't use distance/normalization functions with non-real vector types.

C++ is a bit nice in that respect, in that functions that make absolutely no sense for a given templated data type won't throw errors if you don't call functions that reveal the error-causing segment.
Zakwayda
Zakwayda
My observations will all be pretty standard (similar comments can be found in similar previous threads on the forum), but here they are:

template <typename T>class Vector3D{private:	T x,y,z;protected:public:// Often (but not always), users expect geometric vector classes to// behave like primitive types in that in the absence of explicit// intialization, the object is in an undefined state. This is for// efficiency reasons (although it may or may not matter in practice).// If you want to go this direction, just make the default constructor// do nothing rather than initializing the member fields to zero.	Vector3D();	Vector3D(T,T,T);// The compiler generated copy constructor should be sufficient, I would// think (unless I'm overlooking something).	Vector3D(Vector3D&);//	~Vector3D();// It doesn't look like you've made it possible to modify or query// the x, y, and z elements of the vector directly. This is pretty// common functionality that many users will expect. A couple of options:// - Make the member data public.// - Add accessor functions.// - Grant element access via operator[]().// I think the 'return a reference' thing has already been mentioned.// Also, a lot of these functions (those that are binary in nature)// should probably be non-member (possibly friend, depending) functions.// This would include the binary -, +, /, and * operators, and the// Dot() and Cross() functions as well.	bool		operator==(const Vector3D&) const;	Vector3D<T>	operator+(const Vector3D&) const;	Vector3D<T>	operator-(const Vector3D&) const;	Vector3D<T>	operator-() const;	Vector3D<T>	operator/(float) const;	Vector3D<T>	operator*(float) const;	Vector3D<T>	operator*(const Matrix&) const;	void		operator+=(const Vector3D&);	void		operator-=(const Vector3D&);	T			Dot(const Vector3D&) const;	Vector3D<T>	Cross(const Vector3D&) const;	T			Magnitude() const;	T			Distance(const Vector3D& v) const;	void		Normalize();	void		Set(T,T,T);// As noted previously, you can just use the compiler-generated// '=' operator for this.	void		Set(Vector3D&);	void		Print() const;	friend ostream&	operator<<(ostream& os, const Vector3D&);};template <typename T> Vector3D<T>::Vector3D() : x(0), y(0), z(0) {}// You can just call the arguments x, y, and z - there won't be a name// clash. (Also, in general it's best to avoid leading underscores. I// think they're ok here, but IMO it's easiest just to avoid them// altogether.)template <typename T> Vector3D<T>::Vector3D(T _x, T _y, T _z) : x(_x), y(_y), z(_z) {}template <typename T> Vector3D<T>::Vector3D(Vector3D& v) : x(v.x), y(v.y), z(v.z) {}template <typename T> bool Vector3D<T>::operator==(const Vector3D& v) const{	return (x == v.x && y == v.y && z == v.z);	}template <typename T> Vector3D<T> Vector3D<T>::operator+(const Vector3D& v) const{	return Vector3D( x + v.x,  y + v.y,  z + v.z);	}template <typename T> Vector3D<T> Vector3D<T>::operator-(const Vector3D& v) const{	return Vector3D( x - v.x,  y - v.y,  z - v.z);	}template <typename T> Vector3D<T> Vector3D<T>::operator-() const{	return Vector3D( -x, -y, -z);	}template <typename T> Vector3D<T> Vector3D<T>::operator/(float s) const{	float sInv = 1.0f / s;	return Vector3D( x*sInv, y*sInv, z*sInv);	}template <typename T> Vector3D<T> Vector3D<T>::operator*(float s) const{	return Vector3D( x*s, y*s, z*s);	}template <typename T> Vector3D<T> Vector3D<T>::operator*(const Matrix& m) const{	return Vector3D(x*m.m11 + y*m.m12 + z*m.m13,					x*m.m21 + y*m.m22 + z*m.m23,					x*m.m31 + y*m.m32 + z*m.m33 );}template <typename T> void Vector3D<T>::operator+=(const Vector3D& v){	x+=v.x;	y+=v.y;	z+=v.z;	}template <typename T> void Vector3D<T>::operator-=(const Vector3D& v){	x-=v.x;	y-=v.y;	z-=v.z;	}template <typename T> T Vector3D<T>::Dot(const Vector3D& v) const{	return v.x*x + v.y*y + v.z*z;	}template <typename T> Vector3D<T> Vector3D<T>::Cross(const Vector3D& v) const{	return Vector( y * v.z  -  z * v.y, z * v.x  -  x * v.z, x * v.y  -  y * v.x );	}// As has been mentioned, you should have a MagnitudeSquared() function// as well. The MagnitudeSquared() function should compute the squared// magnitude as Dot(*this, *this). Magnitude() should then be implemented// as sqrt(MagnitudeSquared()). (This is to reduce code duplication.)template <typename T> T Vector3D<T>::Magnitude() const{	return sqrt( x*x + y*y + z*z );	}// You'll want DistanceSquared() also.template <typename T> T Vector3D<T>::Distance(const Vector3D& v) const{	return (*this - v).Magnitude();	}// There are a number of ways to handle an unsuccessful normalization,// but failing silently might not be the best choice. Also, if you're// going to do a check here, it should be against an epsilon value// rather than zero.template <typename T> void Vector3D<T>::Normalize(){		T mag = ( x*x + y*y + z*z );		if (mag == 0) return;		T mul = 1/sqrtf((float)mag);            		x *= mul;	y *= mul;	z *= mul;// This return statement doesn't do anything - you can take it out.		return;}template <typename T> void Vector3D<T>::Set(T _x, T _y, T _z){	x=_x; 	y=_y;	z=_z;	}template <typename T> void Vector3D<T>::Set(Vector3D& v){	x=v.x;	y=v.y;	z=v.z;	}template <typename T> void Vector3D<T>::Print() const{	cout << fixed << "X: " << x << "\tY: " << y << "\tZ: " << z << endl;	}ostream& operator<<(ostream& os, const Vector3D<float>& v){	return os<< fixed << "X: " << v.x << "f\tY: " << v.y << "f\tZ: " << v.z << "f" << endl;}

There's probably more, but that's a start. Also, I wouldn't worry too much about efficiency. The way you're doing things now is pretty standard, and should be plenty fast for most purposes (especially if NRVO is applied). If you end up needing it to be faster than this, you'll probably want to look at assembly-level optimizations or expression templates.
Splinter of Chaos
Splinter of Chaos
If you're going to make x, y, and z private, at least have accessors. What if I hold a position in the vertext, then want to tell openGL where to draw this thing?

And, speaking of OpenGL, they have it so some functions can take arrays. Holding the x, y, and z in an array and having a function like toArray() could be quite useful.

Although, you might be able to just &vec[1] or something, that's not really guaranteed safe.

Do you want the fallowing to work and does it?
    Vector3D v1(1,2,3);    Vector3D v2(1.0,2.2,3.4);    Vector3D<char> v3 = v2 + v1; // Might not be able to convert Vector3D<double> to <int>. 

I suggest changing code like
    template <typename T> Vector3D<T> Vector3D<T>::operator+(const Vector3D& v) const    {	return Vector3D( x + v.x,  y + v.y,  z + v.z);	}
to
    template <typename T, typename Y> Vector3D<T> Vector3D<T>::operator+(const Vector3D<Y>& v) const    {	return Vector3D( x + v.x,  y + v.y,  z + v.z);	}


And, when in the class you have code like Vector3D<T> operator-() const;Note that when C++ sees just the class name with no template parameter inside the class, it assumes you mean of that type. You can just write
    Vector3D	operator-() const;


I found it advantageous to have functions like Normalize() in duplicate; one that alters this vector, one that copies, then alters.

Here's an example of my vector class in action:
                // If there was any input...                if( input ) // Implicate bool conversion.                {                    if( /* shot to be fired */ )                    {                        new Bullet (                            pos + magnitude( input, 15.0f ), // Create temp; modify.                            input.magnitude( BmSpeeds[ bm ] ), // Modifies orig.                            Vector3( 0.0f, 0.0f, input.xyTheta() * R_TO_D )                        );                        // Move in the opposite direction of the bullet; kickback.                        vel = magnitude( input, BmKickBacks[ bm ] );                        rot.z() = input.xyTheta() * R_TO_D;                    }


The implicate bool conversion is supposedly dangerous, but I ran into no problems and the problem code I was told would fail didn't...I think.
Zakwayda
Zakwayda
Quote:
I suggest changing code like
    template <typename T> Vector3D<T> Vector3D<T>::operator+(const Vector3D& v) const    {	return Vector3D( x + v.x,  y + v.y,  z + v.z);	}
to
    template <typename T, typename Y> Vector3D<T> Vector3D<T>::operator+(const Vector3D<Y>& v) const    {	return Vector3D( x + v.x,  y + v.y,  z + v.z);	}
An issue that isn't really addressed in the above example is that of type promotion. (A binary operator that takes as arguments vectors with different element types needs to have a way of determining what type to use for the return value.)
Veslefrikk
Veslefrikk
Thanks for all the replies, there is definatly always something new to learn about this language and how to utilitize it.

Vector class i new and improved now from the suggestions, and I should be a bit wiser :D

Stefan
Splinter of Chaos
Splinter of Chaos
And, one thing I forgot to mention is my recent work in templates has taught me that having a list of typedefs inside the class is always a good thing. In this case, you'd probably want something like typedef T type.
    Vector3D<int> v;    // some code.    Vector3D<int> v2 = v * whatever;

This code works fine until you decide you want to change v's type. Then, v2 would require a conversion. You could, for v2, write some sort of decltype or typeof instead of the Vector3D (although, I can't rememeber quite what either of those exactly do, right now), but the renders your code less readable and I don't believe either are yet standard. You could also use a typedef for int, and there's really no downside to that other than adding yet another type. Still, it's nice to provide an internal typedef.
    Vector3D<int> v;    // some code.    Vector3D<v::type> v2 = v * whatever; // More readable, no?


Quote:
Original post by jyk
Quote:
I suggest changing code like
    template <typename T> Vector3D<T> Vector3D<T>::operator+(const Vector3D& v) const    {	return Vector3D( x + v.x,  y + v.y,  z + v.z);	}
to
    template <typename T, typename Y> Vector3D<T> Vector3D<T>::operator+(const Vector3D<Y>& v) const    {	return Vector3D( x + v.x,  y + v.y,  z + v.z);	}
An issue that isn't really addressed in the above example is that of type promotion. (A binary operator that takes as arguments vectors with different element types needs to have a way of determining what type to use for the return value.)


I believe, because the function is declared in the class as taking a right-hand argument, that the compiler will assume the right-hand operand determines the return type. Of coarse, the upcoming terror of alternate function syntax I think will do a slightly better job.
    template< typename T, typename Y >     auto Vector3D<T>::operator+(const Vector3D<Y>& v) const         -> Vector3D<decltype(this->x+v.x)>    {	return Vector3D<decltype(this->x+v.x)>( x + v.x,  y + v.y,  z + v.z);	}
I don't know if the above is how it should be written, but, that's how I think it would be, if this would even be possible.

EDIT

Reading a thread on this site also about a vector class (a very popular subject!) reminded me that a true STL writer would not write typedef T type, but rather typedef T value_type and typedef T& reference_type and typedef const T...you get the picture.

Honestly, it's not hard to go overboard with the typedefs, but I do think being fairly expressive about all the type info is important. Just wanted to alert you to the extremist views.

[Edited by - Splinter of Chaos on December 11, 2008 12:49:22 AM]
Zahlman
Zahlman
Quote:
Original post by Splinter of Chaos
I believe, because the function is declared in the class as taking a right-hand argument, that the compiler will assume the right-hand operand determines the return type.


Er, no; you declared the return type as Vector3D, so the return type is a Vector3D (i.e. the left-hand argument's type). But regardless, the point is that this kind of behaviour isn't necessarily desirable. Wouldn't you expect both Vector3D + Vector3D and Vector3D + Vector3D to yield a Vector3D, the same way int + double and double + int both yield a double? :)
Splinter of Chaos
Splinter of Chaos
Vector = Vector + Vector

My ideal, C++0x system:
The addition causes a vector float. The assignment requires no conversion. The user never knows of the trickery and the operands could be reversed producing the same code. Exactly how you said it should be.

Unfortunately, this is not syntactically correct in current C++, so I settle for my method of left-hand rules templating. However, if you have a better solution, you could illuminate us all.
rozz666
rozz666
You could create a promotion template e.g.:

template <typename T1, typename T2>struct promote {    typedef T1 result;};// specializations for all basic typestemplate <>struct promote<int, float> { typedef float result; };template <>struct promote<float, int> { typedef float result; };//etc.


The only drawback is that you need to write specializations if you want to use different types.

Maybe there exist better solutions.

[Edited by - rozz666 on December 12, 2008 4:57:07 AM]
Zakwayda
Zakwayda
Quote:
Unfortunately, this is not syntactically correct in current C++, so I settle for my method of left-hand rules templating. However, if you have a better solution, you could illuminate us all.
The 'better' solution is type promotion (as I alluded to in my earlier post). rozz666's post, above, provides an example of how type promotion can be implemented.
Splinter of Chaos
Splinter of Chaos
But it has problems and can't, for example, work with a custom number type, or a type not anticipated by the "library writer" (like if they ever make long long long or if a user SOMEHOW finds a use for Vector3D or something). I like solutions that easily adapt.

As I said, I'm waiting for C++0x's standardization and sticking with my decltype idea.

Although one note on the type promotion: You might be interested to know that you'll do a lot less typing if you do it like this:
template <class T, class U>struct promote : public promote<U,T> {    // The typedef is supplied in the parent.};template <>struct promote<int, float> { typedef float result; };

I was so excited to see my example program compile. TMP is fricken amazing stuff!
rozz666
rozz666
Quote:
Original post by Splinter of Chaos
But it has problems and can't, for example, work with a custom number type, or a type not anticipated by the "library writer" (like if they ever make long long long or if a user SOMEHOW finds a use for Vector3D or something). I like solutions that easily adapt.


It's no problem. You can specialize the promotion traits for your own types.
BTW I'm not sure if the recursive inheritance is good. What will happen if you use the non-specialized version?
Splinter of Chaos
Splinter of Chaos
Sure you can specialize, but having to defeats the purpose. Good OOP design is such that once the library is built, it doesn't have to be altered. It is generic. If I write a big num class, or a bit class, or anything like that, the library should work without any altering. I shouldn't even be required to know about the promote system.

And, yes, the recursive template definition might be irksome, but unlike compiled code, compilers know to look out for recursive templates.

Here's the GCC error message:
C:\Documents and Settings\Owner\Desktop\Programming\C++\WorkSpace\TESTING>g++ testing.cpptesting.cpp: In instantiation of 'promote<int, double>':testing.cpp:7:   instantiated from 'promote<double, int>'testing.cpp:25:   instantiated from heretesting.cpp:7: error: invalid use of incomplete type 'struct promote<double, int>'testing.cpp:7: error: declaration of 'struct promote<double, int>'testing.cpp: In function 'int main()':testing.cpp:25: error: no matching function for call to 'f(double, int)'C:\Documents and Settings\Owner\Desktop\Programming\C++\WorkSpace\TESTING>

The MSVC error is no less helpful, but just a bit longer. It didn't take them long to catch the problem at all.

Oh, and remember that when the compiler sees promote::result, it doesn't actually know that result is a type, so you need to write typename promote::result.
rozz666
rozz666
Quote:
Original post by Splinter of Chaos
Sure you can specialize, but having to defeats the purpose. Good OOP design is such that once the library is built, it doesn't have to be altered. It is generic. If I write a big num class, or a bit class, or anything like that, the library should work without any altering. I shouldn't even be required to know about the promote system.


I don't agree. What about std::iterator_traits?
You don't have to modify the library. You have to specialize the template for your own type just like you specialize e.g. std::vector for your own type if you want to use it. You just have to make it clear in the documentation. Define the promotion rules between your type and other types you want to use the library with.
visitor
visitor
Here's my attempt.

Assuming the template argument of X is either integral or floating point, this should pick floating point types over integral, and larger type over smaller if both fall in the same category for operator+.

It also has conversion constructor, so you can assign the result back to a lesser type, but the calculation itself should be done using higher precision.

If the type of X is other it is possible that it just "works", or you get a nice error along the way.

I'm not much of an expert in this, so this probably could be done more elegantly (Boost.TypeTraits may help but I left them out here) and it probably doesn't cover zany cases well.

#include <iostream>#include <string>#include <typeinfo>template <class T>class X{    T value;public:    X(const T& v = T()): value(v) {}    template <class U>    X(const X& other): value(static_cast<T>(other.value)) {}    T val() const { return value; }};namespace detail{template <class T>struct IsFloatingPoint{    static const bool value = false;};template<>struct IsFloatingPoint<float>{    static const bool value = true;};template<>struct IsFloatingPoint<double>{    static const bool value = true;};template <bool, class T, class U>struct SelectIf{};template <class T, class U>struct SelectIf<true, T, U>{    typedef T type;};template <class T, class U>struct SelectIf<false, T, U>{    typedef U type;};template <bool, bool, class T, class U>struct PromoteX{};template <class T, class U>struct PromoteX<true, true, T, U>{    typedef typename SelectIf<(sizeof(T) > sizeof(U)), T, U>::type type;};template <class T, class U>struct PromoteX<false, false, T, U>{    typedef typename SelectIf<(sizeof(T) > sizeof(U)), T, U>::type type;};template <class T, class U>struct PromoteX<true, false, T, U>{    typedef T type;};template <class T, class U>struct PromoteX<false, true, T, U>{    typedef U type;};} //end detailtemplate <class T, class U>struct PromoteX{    typedef X<typename detail::PromoteX<detail::IsFloatingPoint<T>::value, detail::IsFloatingPoint::value, T, U>::type> type;};//at lasttemplate <class T, class U>typename PromoteX<T, U>::type operator+(const X<T>& lhv, const X& rhv){    typename PromoteX<T, U>::type(lhv.val() + rhv.val());}int main(){    X<int> i_x(10);    X<float> f_x(11.1f);    X<double> d_x(12.2);    X<std::string> s_x("ole"); //stupid thing to do    std::cout << typeid(i_x + f_x).name() << '\n'    << typeid(f_x + d_x).name() << '\n'    << typeid(d_x + d_x).name() << '\n'    //<< typeid(s_x + d_x).name() << '\n' //error: no match for operator+    << typeid(s_x + s_x).name() << '\n';}
Splinter of Chaos
Splinter of Chaos
EDIT:
Ninja'd by Visitor! That's actually a pretty neat system! Although, knowing which to go with based on size isn't always great (since sizeof(int) == sizeof(float), yet int+float promotes to float). Although, I can't find the problem that I predict in my tests, so I guess I just don't understand it.

Also, I can't remember where, but is_integral and floating are defined in both GCC's and MSVC's standard library inside the type_traits header. I think C++0x says enable_if, which is analogous to your SelectIf, will have to be there too, but I only saw it in GCC's lib, and under a different namespace.
template <class T, class U>struct PromoteX{    typedef X<typename detail::PromoteX<detail::IsFloatingPoint<T>::value, detail::IsFloatingPoint::value, T, U>::type> type;};


I wonder if we could change that typedef to
typedef X<typename is_simular_type<T,U>, T, U>::type> type;

with is_simular_type being something like
struct integral_tag {};struct floating_tag {};template< bool, class T, class U > is_simular_type {};template< class T, class U > is_simular_type< std::is_integral<T> && std::is_integral, T, U>{    const static bool value = true;};template< class T, class U > is_simular_type< std::is_floating_point<T> && std:: is_floating_point , T, U>{    const static bool value = true;};

Hmm... I'd love to try this out, but I don't have time. Dinner.
Anyway, cool code!
/EDIT

To rozz666
True, but if I specialize an basic_string, it can specialize any of the internal type_traits or iterator_traits for me. Most of the STL is built so you could live happily ignorant of the fact these trait classes exist. This is good OOP; you only require knowledge of the public interface and do not need to know how it's implemented.

How do you have to specialize iterator_traits, anyway? Usually, I just need to inherit. In fact, since the type has no actual members, I have no idea when you'd really need to specialize it!

Same for std::vector. I've never heard of a time you couldn't just write std::vector. (And, yeah, I know std::vector isn't ideal, but still not worth specialization, is it?)

And, even if by specialize, you mean typedef iterator, it's not like you have to write more than that one line. But, with the promote technique, you can't just write typedef promote { ... }. Then, there are countless other types (short, long char, unsigned short, unsigned long) to specialize it with.

However, it just hit me there might be a way to do this through a much more complex promote system... We could define a can_convert_to_integegral type which would cover every integer type. We'd have to specialize that for every integer as well. Then a can_convert_to_floating type.

Then... Ah!
   promote_from<MyType> : can_convert_to_integegral<MyType>, convertible_to_floating<MyType> {};
Well, it's not ideally simple, but it might work. The promote_from type would in some way implement the promote. The only thing I can't figure out is how we'd make promote_from work with MyOtherType.

...maybe...
typename enable_if< promote_from::to< U >::is_valid, promote_from::to< U > >::type f( T, U ) {}

But, I keep thinking there must be something I'm missing. On either the promote, or the iterator_traits you were talking about.

Side note: This has been a very illuminating discussion so far. Thank you.

[Edited by - Splinter of Chaos on December 15, 2008 5:37:12 PM]
rozz666
rozz666
Quote:
Original post by Splinter of Chaos
This is good OOP; you only require knowledge of the public interface and do not need to know how it's implemented.


std::iterator_traits are part of the public interface.

Quote:
Original post by Splinter of Chaos
How do you have to specialize iterator_traits, anyway? Usually, I just need to inherit. In fact, since the type has no actual members, I have no idea when you'd really need to specialize it!


When you want to use your own iterator type that doesn't have standard typedefs like value_type, etc.

Quote:
Original post by Splinter of Chaos
Same for std::vector. I've never heard of a time you couldn't just write std::vector. (And, yeah, I know std::vector isn't ideal, but still not worth specialization, is it?)


My mistake. I meant instantiation. However, template specialization is a kind of instantiation, isn't it?

Quote:
Original post by Splinter of Chaos
And, even if by specialize, you mean typedef iterator, it's not like you have to write more than that one line. But, with the promote technique, you can't just write typedef promote { ... }. Then, there are countless other types (short, long char, unsigned short, unsigned long) to specialize it with.


There are not countless types. There are only types you want to support.
If you want to make a new intergral/floating point type and you want it to cooperate with basic types, you have to define all the operators for these types. Now, if you want to use a vector library that uses type promotion you have to tell it how to promote your type. The same way as when you make your own iterator type you have to specialize iterator traits. You don't modify the library. You extend it's functionality. See http://en.wikipedia.org/wiki/Open/closed_principle

You can always make a macro if you want to save typing. I know it would be nice to use decltype, I'm waiting for C++0x too, but I know that there will always be things that are not straightforward to implement, doesn't matter what language we use.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.