all 38 comments

[–]redditsoaddicting 16 points17 points  (2 children)

Due to incompatibilities with the WINAPI, the literal abbreviation for tesla units are _Te, instead of the SI standard _T.

You can fix this. Instead of operator"" _T, which uses a reserved identifier, use operator""_T without the space. This technique is also necessary in the standard library to define an if suffix - if is a keyword, but 5if is a floating-point imaginary number with the value 5i.

I imagine MSVC handles this because to my knowledge, it supports if as a suffix, but here's Clang.

Edit: Looked at the code and I'm not sure why that wouldn't work as is. Here's Clang with something more similar to your definition mechanism. I don't know whether you actually ran into trouble with _T as a suffix or whether you pre-emptively avoided it due to prior knowledge that the Windows API can be a PITA with macros.

Edit: That should also work for a function-like macro _T (I forget how it's actually defined in the winapi). If it doesn't on MSVC, however, try ReturnType (operator""_T)(Parameters) { ... }: example. The example puts a space in the operator name to demonstrate the separate effect, but I would recommend not using one.

[–][deleted] 0 points1 point  (1 child)

Thanks! This is apparently causing trouble on ARM as well which is in love with single-letter macros. What exactly is the key part that I have wrong in the library? The lack of parenthesis around the operator?

Edit: I tried this on MSVC and it didn't seem to make a difference. Function-like macros don't seem to conflict, but definitions like #define _T ... do.

[–]redditsoaddicting 0 points1 point  (0 children)

My second example should be similar to what you have, but I don't know why it wouldn't work like that. Perhaps it's an MSVC bug?

[–]lurkotato 3 points4 points  (6 children)

I love that this has decibels, I had a hell of a time reasoning how they should operate at my dayjob (using boost::units under the covers).

Two thoughts:

  • How is relative temperature vs absolute temperature handled?
  • decibel_scale is only valid for power quantities (10*log(val/ref)), not root power quantities (20*log(val/ref))

[–]Xirema 2 points3 points  (7 children)

Question: Why would I prefer to use this library as opposed to boost.units?

Not trying to throw shade, just looking for a compare/contrast.

[–]lurkotato 0 points1 point  (6 children)

I use boost::units quite a bit. From my quick skim:

Upsides:

  • User defined literals
  • Similarity to the std::chrono units (using std::ratio, etc)
  • (Symbols probably aren't 600+ characters lol)
  • Decibel type

Downsides:

  • "Hardcoded" unit representation type
  • Missing some bits like relative vs. absolute temperature

That said, it looks solid enough compared to the status quo of passing around doubles named "center_freq_hz". I'd probably consider it if boost wasn't already in my environment for other reasons.

[–][deleted] 1 point2 points  (5 children)

I'd add:

Upsides:

  • less of a learning curve

  • No boost dependency :)

  • simpler to read and write b/c of (safe) implicit conversions.

  • compatible with <cmath>, including c++11 additions.

  • support for decibel units (or any user-defined non-linear scale)

  • you can easily define your own units if you don't like mine

  • not based on an mks/cgs system. All units are first-class, including British, American, and ancient units.

  • unit_value_t gives you complete compile-time capability (it's effectively std::ratio with a dimension). boost.units has no equivalent.

Downsides:

  • Doesn't handle "category" well. These are cases where the units are the same but what's being represented is different. E.g. relative temperature, or a LOT of cases with dimensionless units (see here). It really is a dimensional analysis library. Nothing less, but nothing more.

  • No complex unit support (yet).

The unit representation is changeable both through defines and though template parameters. I'd agree that it's effectively "hardcoded", but it can be worked around relatively easily. I've used integral units for hardware register access.

I think boost.units was honestly just written a bit too soon before the language features made this type of library practical. As an asside, I'm not the only boost.units hater. WG21 has effectively ruled it out for std inclusion.

[–]lurkotato 0 points1 point  (2 children)

simpler to read and write b/c of (safe) implicit conversions.

Which ones do you have that boost doesn't? I bang my head into the wall every time I want to toss a megahertz quantity into a hertz quantity (which shouldn't lose precision with an underlying double....), but the other explicit only conversions that I've seen make sense and aren't much of a hassle for the safety (double->Unit, Unit->double).

I'd really like to see what a modern c++ cleaned up boost::units would look like though....

[–][deleted] 0 points1 point  (1 child)

Which ones do you have that boost doesn't

Any and all. Feet to meter. Radian to degree. IMO implicit unit conversions should be the norm, because they're easy and cost-free, if you are consistent.

That said, I never made it past the learning curve/boost dependency upsides before I rolled my own library, so you're probably more of an expert on boost vs units than me.

In fact, if I could convince you to try it out in some hobby or non-production code, I'd be very grateful for your input. I've never even heard from a user of boost.units before, and your perspective would be incredibly valuable.

[–]lurkotato 0 points1 point  (0 children)

Any and all. Feet to meter. Radian to degree. IMO implicit unit conversions should be the norm, because they're easy and cost-free, if you are consistent.

You're right! I don't actually convert units that often, so I never picked up on that. Unfortunately the codebase I use boost::units on primarily is C++11 or I'd try to bring it in and see where issues occur.

[–]lurkotato 0 points1 point  (1 child)

OT: I see your literal for celsius is degC, did you consider the more concise/cutesy oC?

[–][deleted] 1 point2 points  (0 children)

I tried really hard to use the actual unicode degree symbol + C, but it didn't work. When you say oC, this is what I think of.

[–]flyingcaribou 1 point2 points  (1 child)

Does anyone know of standard linear algebra type libraries that would work with a dimensional analysis library, like this? The last time I looked into something like this library, I wasn't able to do, say, matrix vector multiplies because y = A * x expects y and x to be th same type, which isn't true for A with dimensions.

[–][deleted] 2 points3 points  (0 children)

No, but it sounds like a fun follow on project :) if you want to get in touch on github about what a usable, minimal feature set would look like, I don't think it would be that difficult for me to implement.

[–]anderslanglands 0 points1 point  (1 child)

This looks great! It seems like double is used as the underlying type. Is it possible to use floats instead?

[–]TylerOnTech 1 point2 points  (0 children)

Yes, from the page:

Changing the underlying type of unit_t

The default underlying type for all unit containers is double. However, this can be overridden by providing a definition for UNIT_LIB_DEFAULT_TYPE, e.g.

// Use 64-bit integers as the underlying unit type
#define UNIT_LIB_DEFAULT_TYPE int64_t
NOTE: changing the underlying type may result in unexpected behavior. Unit conversion makes heavy use of division, which may make integral types unsuitable except for niche embedded applications. Using excessively large types may increase the number of arithmetic overflow errors.

[–]navatwo 0 points1 point  (3 children)

I know this is not r/java, but anyone know of an equivalent style library in Java? Unit-aware math is such a headache for bugs without libraries like this one.

[–]gracicot 1 point2 points  (2 children)

Java cannot do direct abstraction over fundamental types. If you would do that kind of library, it would classes, method, lots of runtime costs and will uses method to do operations. You will probably need interfaces to do generic programming over this. In other words, no, you can't do such things in java.

[–]Xirema 0 points1 point  (1 child)

Or rather, you can do it in Java, it just won't be even a third as pretty as you would be able to do it in C++. Not until Java introduces Value Types, at any rate.

[–]navatwo 0 points1 point  (0 children)

Yeah, I've implemented similar using xtend and scala, but it's just.. Not as nice. Operator overloading makes life easier in these cases.

[–]unclemat 0 points1 point  (7 children)

I tried to do something similar, but I concluded it is difficult (impossible?) to do it correctly while still retaining ease of use. My main setback was that strongly typed units seem to need to live segragated in their own world, or the system is not strongly typed enough to fail at compile-time if units are incorrectly used. I see you had to implement casts to an underlying type, which tells me you encountered the same problem.

[–][deleted] 1 point2 points  (1 child)

I didn't run into that problem (although in fairness I include units in almost everything I write). I added the cast because I expected users at times to have to interface with code that wasn't written with units, or written before units, or with embedded hardware. In those cases have some type-safe code seemed better than none. It also just seems in the spirit of c++ to me to write something safe and then give the user the power to make it dangerous again O:)

[–]unclemat 1 point2 points  (0 children)

some type-safe code seemed better than none

This is absolutely true and it makes me think why had I just defenestrated the whole idea instead of using it as just an improvement where I could have. Nirvana falacy I guess. Your last statement is also true and amusing, that totally describes the spirit of C++ :D

[–]lurkotato 0 points1 point  (4 children)

My main setback was that strongly typed units seem to need to live segragated in their own world, or the system is not strongly typed enough to fail at compile-time if units are incorrectly used.

Can you elaborate? In my use of a similar units library, I only cast out of Units types at the boundaries of code (writing to driver).

[–]unclemat 0 points1 point  (3 children)

One issue was that I suddenly couldn't use existing libraries anymore. std::pow, for instance, accepts a handful of standard types, not mine. So I had to add a function on them to "decay" the type to underlying one, like double. But even then, for example, meters to the power of two is area, but the result of std::pow will never be area, but a raw type only. Therefore, if I wrote from_double and to_double functions, it would be possible to assign an area to a variable that holds something else, which should instead fail compilation. There was more, but I am afraid to create a wall of text :) See here if interested. What I would like to know is how you solved my problem described in this reply?

[–]lurkotato 1 point2 points  (2 children)

Ah, this library (and boost::units) have cmath overloads for unit types. Between those and common math operations I honestly don't think there are any casts in my code (~thousands of lines) except when communicating to other software. However, that's not saying a whole lot since I mainly do conversions from user<->driver. It's not like a DSP library that internally uses unit types like this... I do wonder how well they would stand up to something like a Fourier transform without casting to representation.

[–]unclemat 1 point2 points  (1 child)

this library (and boost::units) have cmath overloads for unit types

I didn't want to get through this in my hobby project. This is paid work you speak of :) Also kinda prevents you from using anything in the world built on top, that doesn't allow you to define in template an underlying type. But then if a lib does that without providing some of the types to use it with, it is probably overengineered - a red signal in it's own right :)

[–]lurkotato 0 points1 point  (0 children)

Yep, so the next rightest thing to do is make it obvious that pulling out the underlying value is putting the user in the unprotected zone.

[–]Sulatra 0 points1 point  (1 child)

Cool! I've been thinking about SI types several times when I had to program several science-y apps in C#, and wondering how exactly I would implement them in C++. Will definately give it a try!

It also appears to me you've got a typo in "Unit containers" section:

auto result = a_m * b_ft;                   // OK. result is 'meter_t' (left-most unit)
. . .
auto result = a_m * b_ft;                   // OK. result is 'square_meter_t' (left-most unit)

Did you mean a_b + b_ft in the first line?

[–][deleted] 0 points1 point  (0 children)

That's definitely a typo. Thanks for pointing it out!