C ++ constructor, one for degrees, one for radians

What would be the best practice in C ++ to define two separate constructors, one of which has an input in degrees and the other in radians? The problem I am doing this straightforwardly is that the signature of both methods is the same for the compiler, although the parameter names identify what they are, and the compiler indicates a re-declaration error. Can this be done without introducing another field?

I can do this using one constructor by adding an additional parameter bool, which will allow me to choose which units are passed and use if in the body.

+7
c ++ constructor
source share
6 answers

Use the named idiom constructor . Make your constructor private and choose the one you prefer for the type of argument, degrees or radians. Then do the appropriately named static member functions that do the necessary conversion.

 class Angle { public: static Angle radians(double r) { return Angle(r); } static Angle degrees(double d) { return Angle(d / 180.0 * PI); } private: double angle_in_radians; Angle(double r) :angle_in_radians(r) {} }; 
+14
source share

What would be the best practice in C ++ to define two separate constructors, one of which has an input in degrees and the other in radians?

Best practice would be to have only one constructor that explicitly accepts only one of them (most likely a radian, since all trigonometric functions work with radians).

You must have additional functions to convert values. One way is to use custom literals 1 .

They are designed to provide equal context values ​​with the specific block to be used.


I would go as follows:

 constexpr long double operator"" _deg ( long double deg ) { return deg2rad(deg); } long double deg2rad(long double deg) { return deg*3.141592/180; } long double rad2deg(long double rad) { return (rad/3.141592)*180; } class Angle { public: /** * Default constructor. */ Angle() : radians() {} /** * Takes the initial angle value as radians. */ Angle(long double rad) : radians(rad) {} // Convenience setters and getters void rad(long double value) { radians = value; } long double rad() const { return radians; } void deg(long double value) { radians = deg2rad(value); } long double deg() const { return rad2deg(radians); } private: long double radians; // Only use radians internally }; int main() { Angle ang1(180_deg); // Initialize using the constexpr conversion long double deg = 0; std::cout << "Enter an angle value in degrees: " << std::flush; std::cin >> deg; Angle ang2(deg2rad(deg)); } 

1 In the examples from the documentation, there is one for converting degrees to radians.

+6
source share

I suggest an approach like

 const auto PI = 22 / 7.0; const struct Degrees { explicit constexpr Degrees(double value): value{value} {} const double value; }; const struct Radians { explicit constexpr Radians(double value): value{value} {} const double value; }; const struct Angle { constexpr Angle(Radians r) : radians{r} {} constexpr Angle(Degrees d) : Angle{Radians{180.0 * (d.value / PI)}} {} const Radians radians; }; decltype(auto) operator <<(std::ostream& out, const Degrees& d) { return out << d.value << " degrees"; } decltype(auto) operator <<(std::ostream& out, const Radians& r) { return out << r.value << " radians"; } decltype(auto) operator <<(std::ostream& out, const Angle& a) { return out << "angle: " << a.radians; } int main() { auto d = Degrees{1}; auto a1 = Angle{Radians{1.65}}; auto a2 = Angle{Degrees{240}}; std::cout << a1 << '\n' << "Press any key to continue...\n"; std::cin.get(); } 

Notice how we made the constructors for Radians and Degrees explicit. This prevents them from being invoked implicitly by requiring the user to announce their intention.

Now in this case, an implicit construction such as

 auto a = Angle{1.65}; // won't compile due to ambiguity 

will result in an ambiguity error, but this may not remain true in many iterations of API releases.

For example, if we removed or changed the visibility of one of the Angle constructors, the above code will compile without errors.

The explicit constructors for Degrees and Radians prevent this danger and make the code more understandable.

We can add user-defined literals as syntactic sugar on top of these types to include more precise notations.

For example:

 constexpr auto operator ""_deg(long double d) { return Degrees(d); } 

and

 constexpr auto operator ""_rad(long double r) { return Radians(r); } 

turn on

 auto a = Angle{240.0_deg}; 

and

 auto a = Angle{1.65_rad}; 

respectively

This works because of the advanced features of constexpr in the latest versions of the language. Since we designated our constructors as compiled over time, they can be used as literal types. Note that my literals are potentially truncated from long double to double , and I have yet to find out why declaring them as long double is required, at least in MSVC 11/19/25505.

Update:

After reading the other answers that I thoroughly enjoyed (thanks everyone), I realized that a lot more compilation time was fine, so I added a slightly revised version, including some of what I found out. It is also lighter than my previous version, and does not use accessors.

The implementation above is the revised version.

+3
source share

Benjamin's variational approach:

 class Angle { public: class Radian; // both with constructor accepting the value class Degree; // and a member to store it - ommitting internals Angle(Radian const& r); Angle(Degree const& d); }; Angle(Angle::Degree(180)); Angle(Angle::Radian(4.7)); 
+1
source share

One option is to use value tags.

 constexpr struct radians_t { constexpr explicit radians_t() = default; } radians; constexpr struct degrees_t { constexpr explicit degrees_t() = default; } degrees; 

Then define your constructors:

 foo(double a, radians_t); foo(double a, degrees_t); 

And called like this:

 foo(90.0, degrees); foo(3.14, radians); 

While superficially similar to the named factory, ambiguity tags are compatible with things like perfect redirection and posting of a new one. For example:

 auto p1 = new (buffer) foo(90.0, degrees); auto p2 = std::make_unique<foo>(3.14, radians); 
+1
source share

At first I will say that this is a more difficult approach, but you can carefully separate the degrees and radians throughout your application using opaque typedefs. This will prevent accidental mixing of values ​​with different units - they become errors at compile time, and not errors at runtime. I have an opaque typedef library that allows me to create numeric opaque typedefs and express the operations allowed on them.

You can make individual degrees and types of radians, with degrees convertible to radians, for example:

 #include "opaque/numeric_typedef.hpp" constexpr auto PI = 22 / 7.0; struct degrees : opaque::numeric_typedef<double, degrees> { using base = opaque::numeric_typedef<double, degrees>; using base::base; }; struct radians : opaque::numeric_typedef<double, radians> { using base = opaque::numeric_typedef<double, radians>; using base::base; constexpr radians(degrees d) : base(d.value/180.0 * PI) { } }; class Angle { public: Angle(radians r) : angle_in_radians(r) { } private: radians angle_in_radians; }; int main() { Angle a(degrees(90.0)); Angle b(radians(1.23)); Angle c(5.0); // compile error } 
+1
source share

All Articles