Units of measurement in C++
Solution 1
I know you mentioned you aren't using C++11 but others looking at this question may be, so here's the C++11 solution using user defined literals:
#include <iostream>
using namespace std;
class Frequency
{
public:
void Print() const { cout << hertz << "Hz\n"; }
explicit constexpr Frequency(unsigned int h) : hertz(h) {}
private:
unsigned int hertz;
};
constexpr Frequency operator"" _Hz(unsigned long long hz)
{
return Frequency{hz};
}
constexpr Frequency operator"" _kHz(long double khz)
{
return Frequency{khz * 1000};
}
int main()
{
Frequency(44100_Hz).Print();
Frequency(44.1_kHz).Print();
return 0;
}
Output:
44100Hz
44100Hz
Solution 2
The Boost "Units" library is great for this type of thing.
http://www.boost.org/doc/libs/1_55_0/doc/html/boost_units.html
Solution 3
You can use the factory design pattern to accomplish what you're looking for. You can create a frequency class with a private constructor and several static methods that will construct the object depending on the units the user wants to use. By keeping the constructor private, the user is forced to declare his units explicitly, which reduces the likelihood of user error.
#include <iostream>
using namespace std;
class frequency
{
public:
static frequency hertz(int hz)
{
return frequency(hz);
}
static frequency kilohertz(double kHz)
{
return frequency(kHz * KHZ_TO_HZ);
}
static frequency rpm(int rpm)
{
return frequency(rpm * RPM_TO_HZ);
}
int hz()
{
return m_hz;
}
private:
static const int KHZ_TO_HZ = 1000;
static const int RPM_TO_HZ = 60;
frequency(int hz) : m_hz(hz)
{
}
int m_hz;
};
int main()
{
wcout << frequency::hertz(44100).hz() << "Hz" << endl;
wcout << frequency::kilohertz(44.100).hz() << "Hz" << endl;
}
Related videos on Youtube
Paweł Stawarz
Creator of the jsonCool library, artist (with an actual degree, I can't believe it too), assistant at the Rzeszow University of technology, former Moderator/Super Moderator/Administrator at GameForge AG. I love computer games and graphics - they let me bypass the limits of this world and let my imagination free.
Updated on July 12, 2022Comments
-
Paweł Stawarz almost 2 years
I'm working on a game engine, and currently I'm stuck designing the IO system. I've made it so, the engine itself doesn't handle any file formats, but rather lets the user implement anything he wants by creating a
*.dll
file with appropriately named functions inside. While that itself wasn't much of a problem, my main concerns are the implications that'll probably be visible during the usage of the engine.I designed a simple
resource
interface as a base class for all the things the user can think of, and I'm trying to extend it by making simple child classes dedicated to the common data types, so the user doesn't have to implement the basics by himself (currently I'm thinking ofaudio
,image
,data
andmesh
). Starting with theaudio
class, I've stumbled on a peculiar problem, while trying to decide in what type should I store information about sampling rate. The usual unit are hertz, so I decided to make it anunsigned int
.However there's a little problem here - what if the user tries to set it in kilohertz? Let's assume some abstract file format can store it in both units for a moment. I've made a simple wrapper class to name the unit type:
class hertz{ private: unsigned int value; hertz(){}; public: operator unsigned int(); hertz(unsigned int value); };
and decided to let the user also use kHz:
class kilohertz{ private: float value; kilohertz(){}; public: operator hertz(); kilohertz(float value); };
While the function inside the
audio
class, which lets the user set the sampling rate is declared astrack& samplingRate(units::hertz rate);
. The user has to call it by explicitly saying what order of magnitude he's using:someAudioFile.samplingRate(hertz(44100)); someAudioFile.samplingRate(kilohertz(44.1));
My question is:
Is there a better way to force the user to use a measurement unit in a simple and elegant way? A design pattern maybe, or some clever use of typedefs?
Please also note that in the process of creating the engine, I may require more units which will be incompatible with Hertz. From the top of my head - I may want the user to be able to set a pixel color both by doing
units::rgb(123,42,120)
andunits::hsl(10,30,240)
.I've tried searching for a viable answer and only found this question, but the OP only wanted orders of magnitude without ensuring the units are not compatible with other ones.
Also please note I'm using the old
C++
version, notC++11
. While posting a solution valid in any version is great, it would be nice if I could also use it :)-
Kerrek SB over 10 yearsCan't you just document your API properly?
-
leetNightshade over 10 yearsYeah, to avoid confusion maybe you should have your API stick to one standard, hertz or kilohertz, not both.
-
Paweł Stawarz over 10 yearsDocumenting my API won't change a thing, since I want the user to be able to use both when it comes to - for example - setting a color. So that's not exactly a solution. Maybe it would work with hertz, but that's only one unit.
-
Keith over 10 yearsLook at boost's units library,boost.org/doc/libs/1_55_0/doc/html/boost_units.html and also user defined literals, en.cppreference.com/w/cpp/language/user_literal.
-
leetNightshade over 10 yearsUser defined literals are only available in C++11, so if you can live with that, this should be a good starting point: akrzemi1.wordpress.com/2012/08/12/user-defined-literals-part-i And some alternatives are listed there.
-
Paweł Stawarz over 10 years@leetNightshade I'm stuck with
C++03
ATM, so that unfortunately won't help me -
Mooing Duck over 10 years"The user has to call it by explicitly saying what order of magnitude he's using" "Is there a better way" No. There is no better way than the correct way. The correct way is to have the units listed. You could use overloads to detect
float
vsunsigned
, but I wouldn't recommend it. Use units.
-
-
Paweł Stawarz over 10 yearsI'm not sure if that's a good idea. I mean - Boost is great and certainly offers a lot, but I think it's like using a road roller to crack a nut in this case. Would preffer to get a simpler solution, that doesn't require additional libraries. Although this is a valid solution, so I'm gonna upvote it for now :)
-
aldo over 10 years@PawełStawarz Agreed. If you're not already using Boost, it can seem expensive/difficult to get started. If you are already using Boost, then it's really easy. Once you get over that "hump" though, there is a whole world full of great stuff awaiting you!
-
Paweł Stawarz over 10 yearsWell... since I can't use C++11, I'll stick with my solution. But I would totally use this if I've had a more recent version of my IDE. So - I'm marking this as the answer, for future generations to know :)
-
Martin Moene almost 8 yearsThere's also PhysUnits-CT-Cpp11, a small C++11, C++14 header-only library for compile-time dimensional analysis and unit/quantity manipulation and conversion. Simpler than Boost.Units, only depends on standard C++ library, SI-only, integral powers of dimensions