patterncppMinor
Strongly-typed angle measures in radians and degrees
Viewed 0 times
anddegreesstronglytypedradiansanglemeasures
Problem
I often work with angles when writing code for 3D rendering and such. Personally, I prefer to measure angles in degrees, but many APIs (including the Standard Library) measure angles in radians. So more than once I have produced bugs by passing an angle in one measure when the function/library was expecting it in
another. These kinds of bugs are not hard to track down, but make you waste time nevertheless.
In account of that, I had the idea of defining two classes to represent each angle measure and disable implicit conversions (perhaps inspired by the time durations of
``
template
class AngleOps
{
public:
//
// Add, subtract and negate angles:
//
DERIVED operator - () const { return DERIVED(-angle); }
DERIVED operator + (const DERIVED & other) const { return DERIVED(angle + other.angle); }
DERIVED operator - (const DERIVED & other) const { return DERIVED(angle - other.angle); }
DERIVED & operator += (const DERIVED & other) { setFloatValue(angle + other.angle); return static_cast(*this); }
DERIVED & operator -= (const DERIVED & other) { setFloatValue(angle - other.angle); return static_cast(*this); }
//
// Multiply and divide angle by a scalar value:
//
DERIVED operator (const float scalar) const { return DERIVED(angle scalar); }
DERIVED operator / (const float scalar) const { return DERIVED(angle / scalar); }
DERIVED & operat
another. These kinds of bugs are not hard to track down, but make you waste time nevertheless.
In account of that, I had the idea of defining two classes to represent each angle measure and disable implicit conversions (perhaps inspired by the time durations of
std::chono). Following are the Radians and Degrees types (header only):``
#ifndef ANGLES_HPP
#define ANGLES_HPP
#include
#include
constexpr float Pi = 3.1415926535897931f;
constexpr float TwoPi = 2.0f * Pi;
constexpr float DegToRad = Pi / 180.0f;
constexpr float RadToDeg = 180.0f / Pi;
//
// Implementation details:
//
namespace internal
{
// ========================================================
// template class AngleOps:
// ========================================================
// Operators and methods shared by both Degrees and Radians` classes.template
class AngleOps
{
public:
//
// Add, subtract and negate angles:
//
DERIVED operator - () const { return DERIVED(-angle); }
DERIVED operator + (const DERIVED & other) const { return DERIVED(angle + other.angle); }
DERIVED operator - (const DERIVED & other) const { return DERIVED(angle - other.angle); }
DERIVED & operator += (const DERIVED & other) { setFloatValue(angle + other.angle); return static_cast(*this); }
DERIVED & operator -= (const DERIVED & other) { setFloatValue(angle - other.angle); return static_cast(*this); }
//
// Multiply and divide angle by a scalar value:
//
DERIVED operator (const float scalar) const { return DERIVED(angle scalar); }
DERIVED operator / (const float scalar) const { return DERIVED(angle / scalar); }
DERIVED & operat
Solution
I think it's generally well written, but there are a few things that concern me. In particular:
Consider throwing exceptions rather than asserts
If we start with 360.0 degrees and add a degree, I'm not sure I'd be happy with an assert versus throwing an exception if I were using these classes. It seems to me that I'd more likely want to handle the condition myself, or even better, be able to set a flag as to whether it's an error condition or not.
Use
Rather than defining
Consider renaming namespace
While it might be descriptive for this one and only instance,
Consider throwing exceptions rather than asserts
If we start with 360.0 degrees and add a degree, I'm not sure I'd be happy with an assert versus throwing an exception if I were using these classes. It seems to me that I'd more likely want to handle the condition myself, or even better, be able to set a flag as to whether it's an error condition or not.
Use
M_PI rather than defining your ownRather than defining
Pi you could simply use M_PI which is defined in math.h (aliased to cmath which you already include).Consider renaming namespace
internalWhile it might be descriptive for this one and only instance,
internal is an awfully generic name to use for a namespace. Consider using something more descriptive.Context
StackExchange Code Review Q#85029, answer score: 6
Revisions (0)
No revisions yet.