C++ template floating point constant parameter

9569bfe5ca7c477bc1e6d9587671bb75
0
kulik 101 May 29, 2006 at 16:08

I wanted to code a measurement unit conversion code to aid me to convert radians to degrees, metres to kilometres, etc.

I didn’t want to polute the code with macros or such so I tried to implement it using metaprogramming.

I defined some basic template classes like BaseUnit and DerivedUnit.
Both take HeldType typename parameter. However DerivedUnit needs some kind of ratio so it’s able to convert itself to BaseUnit.

DerivedUnit has operator BaseUnit() so it can be converted to it.
Both BaseUnit and DerivedUnit can be explicitly set to their HeldTypes (floats, doubles, whatever)

You know, then could just:
typedef BaseUnit<double> Meter;
typedef DerivedUnit<Meter, 1000> Kilometer;
// this is just for simplicity sake, you would have to inherit your units to avoid implicit conversions between various BaseUnits of same HeldType

void something( const Meter& v )
{
// do something
}

int main()
{
doSomething( Kilometer( 2.0 ) );
// the operator Meter() in Kilometer (auto instantiated using templates would convert it to Meter with no problems).
}

This all would work as long as I won’t need any floating point ratio (metres, kilometres, megametres, whatever just use unsigned int ratios). But in fact I need floating point ratios very much (radians vs degrees most notably).

I want to do things like:
typedef DerivedUnit<Radian, 180.0 / PI> Degree;

However ISO C++ deprecated floating point template arguments I don’t know why, I searched internet, mailgroups. It doesn’t seem to be very old change, and noone is missing the feature. There must be some more elegant workaround.

First I though about nominator, denominator solution, however that seems very nasty and unelegant.

EDIT: Forgot to say, my target is gcc 4.1, no need to care about Visual Studio 6.0 or so. It would be great if it worked in newest Visual Studio and if it was ISO C++ compliant.

14 Replies

Please log in or register to post a reply.

340bf64ac6abda6e40f7e860279823cb
0
_oisyn 101 May 29, 2006 at 20:18

However ISO C++ deprecated floating point template arguments I don’t know why

Deprecated? They never existed in the first place. Because integers are exact, floats aren’t. A simple value like 0.4 can’t be represented by a float, so that’s waiting for a disaster to happen. (Maybe gcc supported it as an extension, but deprecated it later on when proven it caused problems?)

A fix that fits in your design would be to make the template parameter a float reference or a function returning a float.

const double RadDegFactor = 180.0 / PI;
typedef DerivedUnit<Radian, RadDegFactor> Degree;
9569bfe5ca7c477bc1e6d9587671bb75
0
kulik 101 May 29, 2006 at 20:30

Thanks a million, that works.

But does that mean, that the constant floating point ratio won’t be compile-time inlined. I mean I only pass reference, so even with -O3 it won’t get into the method’s body (not a big deal though).

Thanks, yes it wasn’t deprecated by ISO C++ (it never existed there) but by gcc - my mistake.

340bf64ac6abda6e40f7e860279823cb
0
_oisyn 101 May 29, 2006 at 20:57

Depends on the compiler really, but a reference to a const float means the compiler can optimize it as it knows it’s value and that it should never change. The same goes for a function btw, if you make it inline it can see that it’ll always return the same value and so it will use that value directly in the code.

2fcd95b0b62d18275c6b5a6f23f29791
0
tbp 101 May 30, 2006 at 12:55

I also really miss that lost functionality of g++, snif.
I understand it could have caused much grief if you got caught with your pants down, and it wasn’t portable at all, but emulating it is a pain.

So until floats get promoted to first class citizen (/me keeps fingers crossed), depending on exactly what you’re trying to acheive you have to kludge around; that generaly means using 32/64bit integers and dirty union tricks. Cumbersome, ugly but portable.

6ad5f8c742f1e8ec61000e2b0900fc76
0
davepermen 101 May 30, 2006 at 19:30

how about two integers to define the ratio?

like.. typedef DerivedUnit< Meter, 1,1000 > MilliMeter; // 1,1000 == 1/1000

6f0a333c785da81d479a0f58c2ccb203
0
monjardin 102 May 30, 2006 at 19:39

That’s fine for meters to millimeters, but what if he needs a length in furlongs or parsecs? ;)

2fcd95b0b62d18275c6b5a6f23f29791
0
tbp 101 May 30, 2006 at 21:41

typedef DerivedUnit<Meter, 201168, 1000> Furlong;
typedef DerivedUnit<Meter, 0x6DA0192FEAB900, 1> Parsec;

64bit are enough to cover the solar system with sub-micron precision - more than 3 bits left at that scale - :ninja:

2fcd95b0b62d18275c6b5a6f23f29791
0
tbp 101 May 31, 2006 at 03:01

Insomnia ftw.
@tbp

depending on exactly what you’re trying to acheive you have to kludge around; that generaly means using 32/64bit integers and dirty union tricks. Cumbersome, ugly but portable.

I’ve just tried those again and even if i’m sure i did, apparently, i’m full of it I can’t escape the integral non-type argument rule because i can’t generate strictly compile time integrals out of floats anymore. :whistle:

@kulik

But does that mean, that the constant floating point ratio won’t be compile-time inlined. I mean I only pass reference, so even with -O3 it won’t get into the method’s body (not a big deal though).

Like .oisyn pointed out, modern compilers are smarter than you think, gcc included.

http://forum.hardware.fr/hardwarefr/Programmation/C-resolu-donnee-static-const-Pi-sujet-88197-2.htm#t1344485
Sorry for the french spammage - out of lazyness- but you shouldn’t need any translation to grok where i was going in that post.

Ef55e1db61c529fd7248ee5b5ea2d9a4
0
Borogove 101 May 31, 2006 at 05:42

Hmm, instead of a template<float> class, maybe you could get some mileage out of a singleton factory class with a float constructor argument? Something like:

UnitFactory<Meter> KilometerFactory(1000.0f);

Meter threeKlicks = KilometerFactory.Get(3.0f); // 3 kilometers in Meter unit

860fe478a2545d6c07b88c759292499e
0
SmokingRope 101 May 31, 2006 at 11:40

This would have the added advantage that you could use the conversion routines with variables which is not possible with metaprograms.

340bf64ac6abda6e40f7e860279823cb
0
_oisyn 101 May 31, 2006 at 12:31

It is with functions or functors.

9569bfe5ca7c477bc1e6d9587671bb75
0
kulik 101 May 31, 2006 at 14:01

@davepermen

how about two integers to define the ratio? like.. typedef DerivedUnit< Meter, 1,1000 > MilliMeter; // 1,1000 == 1/1000

@kulik

First I though about nominator, denominator solution, however that seems very nasty and unelegant.

nominator / denominator = ratio
I will probably go that way

btw: Singleton factory isn’t a bad idea but i want to do this
example:
void Object::setRotation( const Radian& rot );

if you call this with whatever unit it will get automatically converted to Radian. Singleton would be great to parse config files or such, but this could be archived with metaprogramming I think.

thanks for all suggestions

6f0a333c785da81d479a0f58c2ccb203
0
monjardin 102 May 31, 2006 at 17:03

What happens when you define your template like this?

template<typename BASE, const double & RATIO>
class DerivedUnit
{
...
};

typedef UnitBase<float> Meter;
typedef DerivedUnit<Meter, 1000.0> Kilometer;

It works just fine in VC8, but I’m a little confused about where the RATIO is stored. :wacko:

340bf64ac6abda6e40f7e860279823cb
0
_oisyn 101 May 31, 2006 at 17:52

That actually works in VC8? :blink:

It certainly isn’t ISO C++. Furthermore, each translation unit would probably have it’s own Kilometer type.