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

boost::units::quantity should be trivial #58

Open
hlewin opened this issue Dec 8, 2023 · 18 comments
Open

boost::units::quantity should be trivial #58

hlewin opened this issue Dec 8, 2023 · 18 comments

Comments

@hlewin
Copy link

hlewin commented Dec 8, 2023

I think boost::units::quantity<> should be as trivial as possible.

Right now they are not ( std::is_trivial_v is false ) because of more-or-less superfluous implementations of constructors and assignment operators. The internally asserted layout compatibilities have no meaning for users of the class.

I do not really have a strong argument for making them trivial but have a strong gut feeling they should be (for users at least).

@correaa
Copy link

correaa commented Dec 9, 2023

(Disclaimer: I am not a developer of Boost.Units, I am just a user, and I think there are strong arguments for this).

I agree with this.
I think it is a justified design, but history is not on our side.

For a while, I mistakenly thought that it did make sense for quantities to have a (non-trivial) zero initialization because zero values are, well, special for quantities/measurements.
With time, I realized that it is not a good reason, mainly from reading the book "Elements of Programming" and some related talks https://www.youtube.com/watch?v=HcSAIZafNZU "Partially-Formed Objects For Fun And Profit".

Many types are unfortunately stuck on being non-trivial when they shouldn't be, for example, std::pair of trivials or even std::complex.
https://en.cppreference.com/w/cpp/numeric/complex
Non-trivial std::complex, in particular, is a disaster for people doing high-performance numerics.

I don't know why this problem is so prevalent in old (even standard) libraries; I think the problem is that in C++98, it was not very easy to make types trivial (no = default;), so you had to write a lot of class specializations.

This an analogous request I made to have another complex type be trivial: NVIDIA/thrust#1079

And I am afraid what you bring up is exactly the same problem for std::quantity, which I also consider a numeric type.
The problem I observe is that it is almost impossible to change a type and make a trivial after the fact, considering backward compatibility.

As much as I would love std::quantity to be trivial (especially trivially constructor), I hate to be the bearer of bad news: the problem looking forward is that if some people already relied on zero initialization, this will be a breaking change.

Finally, as another datapoint, note that std::chrono::duration (a time quantity implementation), a model for modern C++ classes, is trivial: https://godbolt.org/z/Ej8rKEsG5 .

@hlewin
Copy link
Author

hlewin commented Dec 9, 2023

There could be ifdefs for such things. There are no necessities. Just gut feelings (hope they ate well).

@NAThompson
Copy link

It indeed should be trivial-without it generic code using atomics does not work:

/opt/homebrew/opt/llvm/bin/../include/c++/v1/__atomic/cxx_atomic_impl.h:315:37: fatal error: _Atomic cannot be applied to type 'boost::units::quantity<boost::units::unit<boost::units::list<boost::units::dim<boost::units::length_base_dimension, boost::units::static_rational<2>>, boost::units::dimensionless_type>, boost::units::homogeneous_system<boost::units::list<boost::units::si::meter_base_unit, boost::units::list<boost::units::scaled_base_unit<boost::units::cgs::gram_base_unit, boost::units::scale<10, static_rational<3>>>, boost::units::list<boost::units::si::second_base_unit, boost::units::list<boost::units::si::ampere_base_unit, boost::units::list<boost::units::si::kelvin_base_unit, boost::units::list<boost::units::si::mole_base_unit, boost::units::list<boost::units::si::candela_base_unit, boost::units::list<boost::units::angle::radian_base_unit, boost::units::list<boost::units::angle::steradian_base_unit, boost::units::dimensionless_type>>>>>>>>>>>>' which is not trivially copyable
  315 |   _LIBCPP_DISABLE_EXTENSION_WARNING _Atomic(_Tp) __a_value;
      |                                     ^

@NAThompson
Copy link

@jhunold , @swatanabe : Any hope for getting this patchfile accepted?

make_trivial.patch

@hlewin
Copy link
Author

hlewin commented Feb 10, 2024

If changing behaviour is not an option at all, I'd suggest introducing new namespace for such fixes.

@NAThompson
Copy link

@hlewin : What behavior is changed?

@correaa
Copy link

correaa commented Feb 10, 2024

@NAThompson, as I said (in my convoluted comment), the current behavior initializes the value to zero. A proper trivial type doesn't initialize to zero; it "leaves" the value uninitialized.

If someone's code relied on that default initialization producing a zero value, it would be broken by making the type trivial.

It is unfortunate that quantity was not trivial from the start.

I had a similar dilemma concerning changing behavior.
I made similar changes in an internal version of B.Units.
For example, in most cases, I really wanted implicit conversion between compatible units.

For that, I have boost::units::implicit::quantity, which, in practice, I use as

void(implicit:: quantity<length>)

Analogously, I think what @hlewin is saying is that you can have: boost::units::trivial::quantity with your desired behavior.

@hlewin
Copy link
Author

hlewin commented Feb 10, 2024

Analogously, I think what @hlewin is saying is that you can have: boost::units::trivial::quantity with your desired behavior.

The problem being that boost::quantity would need to inherit from boost::trivial::quantity - not the other way around.

@correaa
Copy link

correaa commented Feb 11, 2024

Yes, I agree.

I was referring to the fact of using a new class only.

That is why trivializing a class after the fact is so difficult.

@hlewin
Copy link
Author

hlewin commented Feb 11, 2024

Yes, I agree.

I was referring to the fact of using a new class only.

That is why trivializing a class after the fact is so difficult.

It is not: Declare the old behaviour deprecated and provide a define like BOOST_REMOVE_DEPRECATED to get rid of the constructors on a voluntary basis.

@correaa
Copy link

correaa commented Feb 11, 2024

@hlewin I couldn't agree more that quantity should be trivial if T is trivial. I am just saying that it would be a breaking change. Moreover, I would agree with the breaking change if it were in my hands.

@hlewin
Copy link
Author

hlewin commented Feb 11, 2024

@hlewin I couldn't agree more that quantity should be trivial if T is trivial. I am just saying that it would be a breaking change. Moreover, I would agree with the breaking change if it were in my hands.

Yeah, point is: Writing a patch to whatever spec is not really a problem. But I am not gonna waste that time if the general sentiment is to not accept it one way or the other. If there are arguments like "someone might have taken the sha256 checksum of that header so changing it might break a build" I am just outta here. I already have this patched in company local versions.

@correaa
Copy link

correaa commented Feb 11, 2024

@hlewin , well, let's see what the developers say, if they say something. (I am not a developer or maintainer of Boost.Units).

I have my own patches add-ons, too.
These are the public ones https://gitlab.com/correaa/boost-unitx (including implicit:: quantity).

@NAThompson
Copy link

My own preference would be to make a breaking change, because well, std::atomic doesn't work with these types due to the triviality requirements, and therefore much of my multithreaded code simply doesn't compile with these types.

@hlewin
Copy link
Author

hlewin commented Feb 12, 2024

Discussing the technicalities of a fix would only make sense if there would be consensus about that this is a problem with someone who can make such decisions. I don't see that. Zero-initialization of numerical values was a javaesque design decision back then and did not age well on the c++ side either. So... I don't hold my breath.
Have a nice day!

@swatanabe
Copy link
Contributor

swatanabe commented Feb 13, 2024 via email

@hlewin
Copy link
Author

hlewin commented Feb 14, 2024

Making it trivial is obviously the right choice if we were implementing it from scratch today, but I'm very hesitant to make such a change, as it would silently cause previously correct code have undefined behavior.

Those concerns can be addressed though, ie the existing behaviour could be preserved for legacy code with a maintenance cost close to zero.

First I already mentioned the possibility of utilizing defines and/or deprecation declarations.
But I think the more elegant solution is the second one.

So from the top of my head I'd propose the following:

  1. provide a class trivial_quantity that contains all the functionality of quantity except the constructors and use that as base class for quantity. This should mostly preserve the behaviour of quantity.
    From here there are different ways to proceed.

2a) At the very least it should now possible for a user of the lib to provide specialisations of quantity without basically copying the complete source-code. Such specialisations could, of course, potentially leak through dependency chains of the eco-system, but then - this is always the case already.
2b) Such a specialisation for trivial types could be provided in an official, optional-to-include header file. This optionally could also provide ways to more easily find usages of the old behaviour to ease migration by utilising deprecation-attributes: It is safe to say, that, for existing legacy code any invocation of the default-constructor would have to be reviewed. So providing an empty default constructor with a deprecation attribute should point users to places to review, I guess. This could also be used for other approaches to the problem btw.
2c) Maybe trivial_quantity could also be used directly by making it playing nicely with the rest of the lib. Either with overloads (much work, but cleaner in a way) or by making it properly auto-convert to and from (const) quantity(&).

Depending on the approach taken one could discuss how and to what extend the library and dependent source-code should be steered towards "the right way".

@NAThompson
Copy link

Making it trivial is obviously the right choice if we were
implementing it from scratch today, but I'm very hesitant to
make such a change, as it would silently cause previously
correct code have undefined behavior.

My impression is that serious users slowly discover all the problems with this library, then either give up or fork the repo-which seems sad given how little effort it would take to fix these small problems.

I'd be very curious to see how many users actually exist and how many give up due to inability to compile large codebases on this type.

IMO the deprecation warning is the way to go and then make a breaking change in 2-3 releases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants