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 inanother. 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 <cmath>#include <cassert>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<DERIVED>:// ========================================================// Operators and methods shared by both `Degrees` and `Radians` classes.template<class DERIVED>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<DERIVED &>(*this); }    DERIVED & operator -= (const DERIVED & other) { setFloatValue(angle - other.angle); return static_cast<DERIVED &>(*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 & operator *= (const float scalar) { setFloatValue(angle * scalar); return static_cast<DERIVED &>(*this); }    DERIVED & operator /= (const float scalar) { setFloatValue(angle / scalar); return static_cast<DERIVED &>(*this); }    //    // Expose the built-in comparison operators:    //    bool operator == (const DERIVED & other) const { return angle == other.angle; }    bool operator != (const DERIVED & other) const { return angle != other.angle; }    bool operator <= (const DERIVED & other) const { return angle <= other.angle; }    bool operator >= (const DERIVED & other) const { return angle >= other.angle; }    bool operator  < (const DERIVED & other) const { return angle  < other.angle; }    bool operator  > (const DERIVED & other) const { return angle  > other.angle; }    //    // Access the underlaying scalar value:    //    float getFloatValue() const    {        return angle;    }    void setFloatValue(const float ang)    {        assert(DERIVED::isValidAngle(ang) &&            Value is not in a valid range to be used as a degrees or radians amount!);        angle = ang;    }protected:    AngleOps() : angle(0.0f) { }    // The actual angle. Interpreted as radians or degrees,    // depending on the class that implements this.    float angle;};} // namespace internal {}class Radians;class Degrees;// ========================================================// class Radians:// ========================================================class Radians final    : public internal::AngleOps<Radians>{public:    explicit Radians(float radians);    explicit Radians(const Degrees & degrees);    Degrees toDegrees() const;    // Sine/cosine/tangent of this angle:    float sin() const;    float cos() const;    float tan() const;    // To angle:    static Radians asin(float sine);    static Radians acos(float cosine);    static Radians atan(float tangent);    static Radians atan(float x, float y);    // Map an angle in radians to the [0,2pi] range.    static Radians normalizeAngleTwoPi(float radians);    // Map an angle in radians to the [-pi,+pi] range.    static Radians normalizeAnglePi(float radians);    // Test if an (absolute) float value is in the [0,2pi] range.    static bool isValidAngle(float radians);};// ========================================================// class Degrees:// ========================================================class Degrees final    : public internal::AngleOps<Degrees>{public:    explicit Degrees(float degrees);    explicit Degrees(const Radians & radians);    Radians toRadians() const;    // Sine/cosine/tangent of this angle:    float sin() const;    float cos() const;    float tan() const;    // To angle:    static Degrees asin(float sine);    static Degrees acos(float cosine);    static Degrees atan(float tangent);    static Degrees atan(float x, float y);    // Map an angle in degrees to the [0,360] range.    static Degrees normalizeAngle360(float degrees);    // Map an angle in degrees to the [-180,+180] range.    static Degrees normalizeAngle180(float degrees);    // Test if an (absolute) float value is in the [0,360] range.    static bool isValidAngle(float degrees);};// ========================================================// Radians inline methods:// ========================================================inline Radians::Radians(const float radians){    setFloatValue(radians);}inline Radians::Radians(const Degrees & degrees){    setFloatValue(degrees.toRadians().angle);}inline Degrees Radians::toDegrees() const{    return Degrees(angle * RadToDeg);}inline float Radians::sin() const{    return std::sin(angle);}inline float Radians::cos() const{    return std::cos(angle);}inline float Radians::tan() const{    return std::tan(angle);}inline Radians Radians::asin(const float sine){    return Radians(std::asin(sine));}inline Radians Radians::acos(const float cosine){    return Radians(std::acos(cosine));}inline Radians Radians::atan(const float tangent){    return Radians(std::atan(tangent));}inline Radians Radians::atan(const float x, const float y){    return Radians(std::atan2(x, y));}inline Radians Radians::normalizeAngleTwoPi(float radians){    if (radians >= TwoPi || radians < 0.0f)    {        radians -= std::floor(radians * (1.0f / TwoPi)) * TwoPi;    }    return Radians(radians);}inline Radians Radians::normalizeAnglePi(float radians){    radians = normalizeAngleTwoPi(radians).angle;    if (radians > Pi)    {        radians -= TwoPi;    }    return Radians(radians);}inline bool Radians::isValidAngle(const float radians){    return std::fabs(radians) <= TwoPi;}// User defined suffix `_rad` for literals with type `Radians` (C++11).inline Radians operator  _rad (long double radians){    // Note: The standard requires the input parameter to be `long double`!    return Radians(static_cast<float>(radians));}// ========================================================// Degrees inline methods:// ========================================================inline Degrees::Degrees(const float degrees){    setFloatValue(degrees);}inline Degrees::Degrees(const Radians & radians){    setFloatValue(radians.toDegrees().angle);}inline Radians Degrees::toRadians() const{    return Radians(angle * DegToRad);}inline float Degrees::sin() const{    return std::sin(angle * DegToRad);}inline float Degrees::cos() const{    return std::cos(angle * DegToRad);}inline float Degrees::tan() const{    return std::tan(angle * DegToRad);}inline Degrees Degrees::asin(const float sine){    return Degrees(std::asin(sine) * RadToDeg);}inline Degrees Degrees::acos(const float cosine){    return Degrees(std::acos(cosine) * RadToDeg);}inline Degrees Degrees::atan(const float tangent){    return Degrees(std::atan(tangent) * RadToDeg);}inline Degrees Degrees::atan(const float x, const float y){    return Degrees(std::atan2(x, y) * RadToDeg);}inline Degrees Degrees::normalizeAngle360(float degrees){    if (degrees >= 360.0f || degrees < 0.0f)    {        degrees -= std::floor(degrees * (1.0f / 360.0f)) * 360.0f;    }    return Degrees(degrees);}inline Degrees Degrees::normalizeAngle180(float degrees){    degrees = normalizeAngle360(degrees).angle;    if (degrees > 180.0f)    {        degrees -= 360.0f;    }    return Degrees(degrees);}inline bool Degrees::isValidAngle(const float degrees){    return std::fabs(degrees) <= 360.0f;}// User defined suffix `_deg` for literals with type `Degrees` (C++11).inline Degrees operator  _deg (long double degrees){    // Note: The standard requires the input parameter to be `long double`!    return Degrees(static_cast<float>(degrees));}#endif // ANGLES_HPPHere is a snippet just to illustrate how it works:void fn_radians(const Radians &) { }void fn_degrees(const Degrees &) { }int main(){    fn_radians(Radians(-Pi));    fn_radians(-3.141592_rad);    fn_degrees(Degrees(128.0f));    fn_degrees(128.0_deg);    auto degs = Degrees(360.0f);    degs -= 1.0_deg;                  // Minus one degree, OK    degs /= 2.0f;                     // Divide angle by two, OK    degs = degs + 60.0_deg;           // Add a few more degrees, OK    degs = degs * 0.2f;               // Multiply by scalar, OK//  degs += 128.0f;                   // ERROR, can't add angle with scalar    auto rads = Radians(Pi);    rads *= 1.5f;                     // Multiply by scalar, OK    rads = rads - Radians(Pi / 2.0f); // Angle difference, OK//  rads += degs;                     // ERROR, different types of angles//  rads = rads * rads;               // ERROR, can't multiply angle with angle//  auto badAngleDegrees = 400.0_deg; // Should trigger an assertion; abs(angle) > 360//  auto badAngleRadians = 180.0_rad; // Should trigger an assertion; abs(angle) > 2*pi}So what do you guys think? Is this a good idea, or just a waste of time? I'm I on the right track?Other concerns I have:Any corner cases I'm missing on implicit conversions that might break the whole concept of strongly typed angle measures?What do you think of being able to multiply by a scalar? AFAIK, you can't multiply angles with angles, but being able to scale an angle is quite useful sometimes.Any other comments on style and correctness are very welcome.
Strongly-typed angle measures in radians and degrees
c++;c++11;mathematics;template meta programming
I think it's generally well written, but there are a few things that concern me.  In particular:Consider throwing exceptions rather than assertsIf 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.