Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Should math.h be implemented as hidden friends? #669

Open
mpusz opened this issue Jan 24, 2025 · 11 comments
Open

Should math.h be implemented as hidden friends? #669

mpusz opened this issue Jan 24, 2025 · 11 comments
Labels
design Design-related discussion good first issue Good for newcomers question Further information is requested refactor Modify existing code potentially causing breaking changes
Milestone

Comments

@mpusz
Copy link
Owner

mpusz commented Jan 24, 2025

Should we implement the functions from the mp_units/math.h header as non-member hidden friend functions in quantity and quantity_point?

@mpusz mpusz added good first issue Good for newcomers question Further information is requested refactor Modify existing code potentially causing breaking changes labels Jan 24, 2025
@chiphogg
Copy link
Collaborator

Maybe!

The main benefit is that it would let us automatically support types that implicitly convert to, but not from, a quantity or quantity_point. (That said, mp-units has traditionally aligned itself against using this sort of type: zero was rejected, and mp-units constants do not convert to quantities.)

Another benefit is reducing the size of overload sets, which can make for shorter compiler error messages and, I think, faster compile times in a few cases.

The downside is that it's an intrusive change: we need to open up the guts of quantity and quantity_point to add it. Also, it locks us in to forcing a <cmath> dependency on all users who include these files. (Maybe this is already the case; I didn't check.)

Au has done this for min, max, clamp, and %, without the <cmath> dependency. We get a lot more benefit out of it than mp-units would, because of our "shapeshifter types" (i.e., Zero and Constant<U>).

Later on, I tried using this for remainder (which wraps std::remainder), passing a Constant as one of the arguments, and it failed. I realized that in order to get this to work, we'd need to take this hidden friend approach for remainder. It felt like opening up a bit of a Pandora's box, so I didn't add it at the time, and still haven't added it. I'm not sure yet how to decide which ones should or shouldn't get this treatment.

@chiphogg
Copy link
Collaborator

To be concrete: which specific functions were you considering?

@JohelEGP JohelEGP changed the title Should math.h be implemented as non-member functions? Should math.h be implemented as hidden friends? Jan 25, 2025
@mpusz
Copy link
Owner Author

mpusz commented Jan 26, 2025

I thought about most if not all, functions from the mp_units/math.h header. Additionally, things like lerp and midpoint should be provided for quantity_point.

@mpusz
Copy link
Owner Author

mpusz commented Jan 26, 2025

and mp-units constants do not convert to quantities

Are constants in Au modeled as units that convert to quantities?

@chiphogg
Copy link
Collaborator

and mp-units constants do not convert to quantities

Are constants in Au modeled as units that convert to quantities?

No, they are constants that convert to quantities. 🙂 Constant<U> is a template that is templated on the unit U, and has a perfect conversion policy (no heuristic overflow safety surface needed) with to any Quantity<OtherU, R>: every lossy conversion fails, and every lossless conversion succeeds.

@mpusz
Copy link
Owner Author

mpusz commented Jan 27, 2025

But I assume that such a constant is a part of the unit of a quantity and simplifies at compile-time when needed?

@chiphogg
Copy link
Collaborator

But I assume that such a constant is a part of the unit of a quantity and simplifies at compile-time when needed?

I don't understand what you're asking, but I understand what Au does. 🙂 Every Constant<U> is templated only on the unit U; therefore, we need to have (or create) a unit for each constant. Constant<U> is a monovalue type, so this unit basically is its value: its value is "1 U", so to speak. If you try to assign this constant to a Quantity<U1, R1>, this assignment will succeed if 1 U can be exactly represented in Quantity<U1, R1>, and fail otherwise. And if it succeeds, the value in U1 and R1 will be computed at compile time.

The parts I didn't understand or found confusing were:

  • "such a constant is a part of the unit of a quantity"
  • "simplifies at compile-time when needed"

I hope my above paragraph answered the question.

@mpusz
Copy link
Owner Author

mpusz commented Jan 27, 2025

I mean something like this:

constexpr auto c = speed_of_light;
quantity q1 = 0.4 * c;
quantity q2 = q1 / c;
  1. What is the unit of q1 (is it c or some other arbitrary unit of speed together with a proper compile-time magnitude)?
  2. I assume that q1 stores just 0.4, and no calculations are done at this point.
  3. I assume that q2 is a dimensionless quantity with exactly a value 0.4, and there was no runtime cost of the division in the initializer.

@chiphogg
Copy link
Collaborator

Thanks for giving a concrete example. I think the answers will be just as you expect (with the caveat that this syntax wouldn't work for Au because we don't have deduction guides for C++17's CTAD):

  1. The units of q1 are the units of c.
  2. Yep, we store just 0.4 under the hood, with no calculations.
  3. Yep, q2 would be just a dimensionless quantity with exactly 0.4, and no runtime cost.

I'm a little uncertain for (3) as to whether we would produce a quantity or just a raw double, but if it's the latter then it's what we now consider a design defect and we plan to fix it in the next "breaking change" release (probably 0.5.0).

@mpusz
Copy link
Owner Author

mpusz commented Jan 28, 2025

Thanks. So, it seems the only drawback of this solution to what we have in mp-units is that a possibly long magnitude of a constant is visible in a quantity type. When two constants are involved, magnitude no longer has a "familiar" value. But you gained implicit constructions from a constant.

I also think that Au uses implicit conversions for operators. It means that things like c - 0.5 * c and -0.5 * c + c will probably work? In our case, where we do not have an implicit conversion and arguments are deduced function template parameters, we would need to add dedicated overloads for constants.

@chiphogg
Copy link
Collaborator

I also think that Au uses implicit conversions for operators. It means that things like c - 0.5 * c and -0.5 * c + c will probably work?

Apparently so!

@mpusz mpusz added the design Design-related discussion label Feb 10, 2025
@mpusz mpusz added this to the v2.5.0 milestone Feb 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design Design-related discussion good first issue Good for newcomers question Further information is requested refactor Modify existing code potentially causing breaking changes
Projects
None yet
Development

No branches or pull requests

2 participants