Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411520 Posts in 69380 Topics- by 58436 Members - Latest Member: GlitchyPSI

May 01, 2024, 02:56:41 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Cheating template specialization
Pages: [1] 2
Print
Author Topic: Cheating template specialization  (Read 3133 times)
Kekskiller
Guest
« on: April 18, 2010, 11:53:28 AM »

I'm redesigning all my color/vector/rect template classes and wondered how to cope with class/type-specific zero values, C functions etc. What I do have at the moment is a set of very similar classes (a Int3D and Float3D class for position and vector calculations). What I want to do now is to create a single template for both having the same methods. Problem is there are some things which bother me like when using integer zeros as initial value for all possible templates. When using a float as template type this could (and would I guess) cause a cast from integer's 0 to float's 0.0 which isn't really elegant nor good for performance. Correct my if I'm wrong. Second problem is when using functions from <math.h> and such (which are only for double or float).

The second can be solved by carefully adding casts to the function call. So my only problem at the moment is how remove the 0/0.0 conversion. I thought about using specialized template function - one for integer/non-float types and a specialization for floats. Using these as specific value-returning initializers would work great if it weren't impossible for GCC to use it.

So... any ideas to work around? A cheaty template trick or a better, cleaner idea?
Logged
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #1 on: April 18, 2010, 12:41:42 PM »

For any built-in data type, the "default constructor" form gives you the default value.  So given a template like so:

Code:
template <typename Type>
void Funk()
{
    Type val = Type(); // Initialize to default value.
}

This will solve your default value problem.

As for the other problem, try using the C++ header <cmath> instead of the C header <math.h>.  I think <cmath> has some additional overloads, but I may be wrong.
Logged



What would John Carmack do?
BorisTheBrave
Level 10
*****


View Profile WWW
« Reply #2 on: April 18, 2010, 02:26:13 PM »

The above solution is best, but I'd bet even initializing both with "0" would optimize to similar code.
Logged
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #3 on: April 18, 2010, 04:04:13 PM »

The above solution is best, but I'd bet even initializing both with "0" would optimize to similar code.

That doesn't work for types that don't accept 0 as an initializer (most user-defined types, I reckon).
Logged



What would John Carmack do?
Will Vale
Level 4
****



View Profile WWW
« Reply #4 on: April 18, 2010, 04:17:17 PM »

Using enums is a handy way to invoke specific constructors, good for things like matrices which have additive and multiplicative identities. Plus you might want to leave the default constructor for simple value types empty for performance reasons.

Code:
enum ZeroTag { ZERO };
enum OneTag { ONE };

template <typename T> class Matrix
{
    Matrix( ZeroTag ) /* init NxN zeroes */ {}
    Matrix( OneTag ) /* init NxN identity */ {}
};

Matrix zero(ZERO);
Matrix one(ONE);

On your other point, I think this is perhaps a sign that what you're trying to do might not be a good idea. I'd be wary of casting back and forth between floats in the integer version since you could get precision errors, which should never happen with integers.

To be honest I think you might be better off keeping them as separate types. If you've already implemented them, what's the gain in joining them together, especially given that all the operations which are relevant for a float vector might not be relevant for an int vector?

Cheers,

Will

Logged
TobiasW
Level 8
***


This can only end brilliantly!


View Profile WWW
« Reply #5 on: April 18, 2010, 04:41:46 PM »

How about a non-member template function returning the zero value/object? You can reuse it among various classes and templates and it is very easy to write new specializations for it without needing to change the template class(es) where it is used.
Logged

Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #6 on: April 18, 2010, 04:57:53 PM »

Using enums is a handy way to invoke specific constructors, good for things like matrices which have additive and multiplicative identities. Plus you might want to leave the default constructor for simple value types empty for performance reasons.

Code:
enum ZeroTag { ZERO };
enum OneTag { ONE };

template <typename T> class Matrix
{
    Matrix( ZeroTag ) /* init NxN zeroes */ {}
    Matrix( OneTag ) /* init NxN identity */ {}
};

Matrix zero(ZERO);
Matrix one(ONE);

C++11 (I think that's the official name now) is adding the ability to create instance of objects that are treated as compile-time constants, that is all of their data is known and can be optimized at compile time, in assignment for example.  I don't remember the syntax (it has something to do with the new keyword constexpr) but I would think it would render some of those techniques obsolete.
Logged



What would John Carmack do?
Rusky
TIGBaby
*


View Profile WWW
« Reply #7 on: April 18, 2010, 05:31:51 PM »

When initializing something with 0, it just means that type's default value. There will be absolutely no runtime cost when initializing a float this way. The Type() method works as well- there shouldn't be any speed problems there.

The <cmath> functions are defined for floating-point types only, but it doesn't really make sense to use them with integers anyway. They will almost always return non-integer values. You generally want to use floating point in situations where you want to use those functions.
Logged

Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #8 on: April 18, 2010, 05:33:58 PM »

When initializing something with 0, it just means that type's default value.

Not for user-defined types.  Type() works for both user-defined and built-in types, 0 does not.
Logged



What would John Carmack do?
Will Vale
Level 4
****



View Profile WWW
« Reply #9 on: April 18, 2010, 06:22:50 PM »

When initializing something with 0, it just means that type's default value.

When constructing an instance with zero, it means roughly "cast the integer zero to something a constructor for this type accepts, if one exists, and store this value in the instance". If no suitable constructor is available, you'll get a compile time error.

Quote
There will be absolutely no runtime cost when initializing a float this way.

<pedantic>There is always a (very small) runtime cost to *store* the value. The cast is usually free, assuming it's not a cast to some complex user type.</pedantic>

Quote
The Type() method works as well

If you use Type(), you get a default-constructed instance, which is not the same thing. Specifically, the value of a default-constructed built-in type is undefined. For a user-created typed it'll depend on what the types's default constructor does. If no default constructor is available (e.g. if it's absent or private) you'll get a compile time error.

Not initialising things is a common cause of "it runs OK in debug" bugs - many debug runtime libraries backfill memory with a pattern like 0xCD or 0xFF, whereas the release versions don't so you get whatever was there before - often zero.

HTH,

Will

Logged
Kekskiller
Guest
« Reply #10 on: April 18, 2010, 10:38:37 PM »

For any built-in data type, the "default constructor" form gives you the default value.  So given a template like so:

Code:
template <typename Type>
void Funk()
{
    Type val = Type(); // Initialize to default value.
}

This. Perfect answer. Tried it, works great! Didn't know it works with buildin ones, too. And I finally understood whats behind the int(...) cast - it's like creating a new object out of the original value, except that it's casted.

This will solve your default value problem.

As for the other problem, try using the C++ header <cmath> instead of the C header <math.h>.  I think <cmath> has some additional overloads, but I may be wrong.

Nope, at a maximum only floats and doubles for both libraries. Or atleast for the C++ variant.

On your other point, I think this is perhaps a sign that what you're trying to do might not be a good idea. I'd be wary of casting back and forth between floats in the integer version since you could get precision errors, which should never happen with integers.

 If you've already implemented them, what's the gain in joining them together, especially given that all the operations which are relevant for a float vector might not be relevant for an int vector?

I only use float and int for coordinates. And convert between them to get a) less details due to conversion or b) more details due to later operations. There is no case where I loose wanted details (otherwise it wouldn't the great way it works atm Wink ). See, I have a grid in my game and every object has an int value (for rastering and faster access) and a float versions (for physics). I only the float versions for physics with no unwanted loss when converting back to int (dropping the float details is what I want at this point since each int has).

And NO, I won't discuss whether it's good or not to store grid/physics seperation like that.

More specific, this is (was!) my problem:

Code:
int Distance2D() {return int(sqrt(pow(float(x),2.0)+pow(float(y),2.0)));}

It's a method of my 3D vector class (x,y,z...), the "Int3D" variant. My idea is to convert into something like different, like:

Code:
T Distance2D() {return T(sqrt(pow(float(x),2.0)+pow(float(y),2.0)));}

or

Code:
T Distance2D() {return T(std::sqrt(pow(float(x),2.0)+pow(float(y),2.0)));}

In this way I would a) guerantee that every possible int gets in the correct float format and every resulting value will be converted back to it's right place. Before rewriting the code I only used the int version, which was my only concern. If I want to calculate the distance using integer I always have to use floats using this C method! Therefore, if I wanted a float distance for integers I would just convert them to float and then use the method. Otherwise I can make use of the int variant which won't be a problem with my program. I also guess the float(float) cast WILL be optimized by the compiler since it's obviously (and logically) superfluous.

To be honest I think you might be better off keeping them as separate types.

I kept them as seperate types before and it was more than annoying to maintain all them when adding a new feature relevant to all of them. They are so similar and I'v never used calculations which won't work for both int and float. It's the way to go. There is no reason to not using one template for this project, a birectional thing.
Logged
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #11 on: April 18, 2010, 11:13:55 PM »

For any built-in data type, the "default constructor" form gives you the default value.  So given a template like so:

Code:
template <typename Type>
void Funk()
{
    Type val = Type(); // Initialize to default value.
}

This. Perfect answer. Tried it, works great! Didn't know it works with buildin ones, too. And I finally understood whats behind the int(...) cast - it's like creating a new object out of the original value, except that it's casted.

It isn't a cast, it's the creation of a temporary via a constructor.

Code:
int(4.0); // Temporary int from value 4.0.
(int)4.0; // C-style cast of the value 4.0.

For the built-in types, there is no difference between the two that I'm aware of, but for user-defined types they can have very different behavior (conversion operators come into play) and it's important to know the difference.
Logged



What would John Carmack do?
Kekskiller
Guest
« Reply #12 on: April 18, 2010, 11:26:11 PM »

It isn't a cast, it's the creation of a temporary via a constructor.

Aww, come one you nitpicker! You'll atleast need a cast to convert a value from one to another. May it be within the constructor or outside of it.

* Kekskiller casts an integer flameball on Average Software's nitpicking unit
Logged
Kekskiller
Guest
« Reply #13 on: April 19, 2010, 04:11:57 AM »

For everyone who's interested: My final solution is a a bit more complex than I thought cause I also wanted the possiblity to add floats to integers, etc.

Code:
template <class T> struct Vector3D {
  // a vector class containing type-independent calculations like *,/,+,- etc and such
  // except constructors (this class isn't supposed for direct use)
};

template <class T> struct Pos3D: public Vector3D<T> {
  // only type-independent constructors
};
template <> struct Pos3D<int>: public Vector3D<int> {
  // constructors for int AND float parameters
}
template <> struct Pos3D<float>: public Vector3D<float> {
  // constructors for float AND int parameters
};

This way I can put all normal calculations into Vector3D and all initializers/type-specific methods/constructors into the specialized templates. So far it seems the only way cover such things to me. Atleast I don't need to duplicate ALL methods/operations like when using specialization of non-inherited classes.

Hooray for templates and classes.
« Last Edit: April 19, 2010, 04:22:23 AM by Kekskiller » Logged
BorisTheBrave
Level 10
*****


View Profile WWW
« Reply #14 on: April 19, 2010, 12:39:11 PM »

Actually, why the heck do you want integer vectors? They are useless for virtually everything, except as array indexes.
Logged
Mikademus
Level 10
*****


The Magical Owl


View Profile
« Reply #15 on: April 19, 2010, 01:00:00 PM »

Actually, why the heck do you want integer vectors? They are useless for virtually everything, except as array indexes.

One reason I can think of at the top of my head is for "typedef Vector<int, 2> ScreenCoordinate;", where you already have all mathematical operators and implicit conversions defined.
Logged

\\\"There\\\'s a tendency among the press to attribute the creation of a game to a single person,\\\" says Warren Spector, creator of Thief and Deus Ex. --IGN<br />My compilation of game engines for indies
BorisTheBrave
Level 10
*****


View Profile WWW
« Reply #16 on: April 19, 2010, 01:49:42 PM »

I guess for pixel perfect co-ordinates, but I'd still use generally floats and then cast to ints at the last minute.
Logged
Kekskiller
Guest
« Reply #17 on: April 19, 2010, 01:56:10 PM »

I guess for pixel perfect co-ordinates, but I'd still use generally floats and then cast to ints at the last minute.

Oh, thats such a waste.
Logged
Tycho Brahe
Level 10
*****

λx.x


View Profile
« Reply #18 on: April 19, 2010, 02:05:43 PM »

I guess for pixel perfect co-ordinates, but I'd still use generally floats and then cast to ints at the last minute.
I'd have thought that wouldnt be very good for memory usage? would it? they generally (for single precision float vs short int) use about double the number of bits.
Logged
drChengele
Level 2
**


if (status = UNDER_ATTACK) launch_nukes();


View Profile
« Reply #19 on: April 19, 2010, 02:25:42 PM »

I guess for pixel perfect co-ordinates, but I'd still use generally floats and then cast to ints at the last minute.
Not that I don't do the same when I need it, but it is worth remembering that a float to int conversion is about 10 times slower (50 instructions long compared to 4-5, I forget the exact numbers) for int to float. That may not seem like much on modern processors, but it's only a matter of time before you get the chance to use it in a time-critical section of the code which executes in exponential time or something like that.
Logged

Praetor
Currently working on : tactical battles.
Pages: [1] 2
Print
Jump to:  

Theme orange-lt created by panic