c++physicsboost-units

How to store a boost::quantity with possible different boost::dimension


I am using boost::units library to enforce physical consistency in a scientific project. I have read and tried several examples from boost documentation. I am able to create my dimensions, units and quantities. I did some calculus, it works very well. It is exactly what I expected, except that...

In my project, I deal with time series which have several different units (temperature, concentration, density, etc.) based on six dimensions. In order to allow safe and easy units conversions, I would like to add a member to each channel class representing the dimensions and units of time series. And, data treatment (import, conversion, etc.) are user-driven, therefore dynamic.

My problem is the following, because of the boost::units structure, quantities within an homogeneous system but with different dimensions have different types. Therefore you cannot directly declare a member such as:

boost::units::quantity channelUnits;

Compiler will claim you have to specify dimensions using template chevrons. But if you do so, you will not be able to store different type of quantities (say quantities with different dimensions).

Then, I looked for boost::units::quantity declaration to find out if there is a base class that I can use in a polymorphic way. But I haven't found it, instead I discovered that boost::units heavily uses Template Meta Programming which is not an issue but does not exactly fit my dynamic needs since everything is resolved at compile-time not at run-time.

After more reading, I tried to wrap different quantities in a boost::variant object (nice to meet it for the very first time).

typedef boost::variant<
   boost::units::quantity<dim1>,
   ...
> channelUnitsType;
channelUnitsType channelUnits;

I performed some tests and it seems to work. But I am not confident with boost::variant and the visitor-pattern.

My questions are the following:


Solution

  • I have been thinking about this problem and came up with the following conclusion:

    1. Implement type erasure (pros: nice interfaces, cons:memory overhead)

    It looks impossible to store without overhead a general quantity with common dimension, that break one of the design principles of the libraries. Even type erasure won't help here.

    2. Implement a convertible type (pros: nice interfaces, cons:operational overhead)

    The only way I see without storage overhead, is to choose a conventional (possibly hidden) system where all units are converted to and from. There is no memory overhead but there is a multiplication overhead in almost all queries to the values and a tremendous number of conversion and some loose of precision of high exponent, (think of conversion from avogadro number to the 10 power).

    3. Allow implicit conversions (pros: nice interfaces, cons:harder to debug, unexpected operational overheads)

    Another option, mostly in the practical side to alleviate the problem is to allow implicit conversion at the interface level, see here: https://groups.google.com/d/msg/boost-devel-archive/JvA5W9OETt8/5fMwXWuCdDsJ

    4. Template/Generic code (pros: no runtime or memory overhead, conceptually correct, philosophy follows that of the library, cons: harder to debug, ugly interfaces, possible code bloat, lots of template parameters everywhere)

    If you ask the library designer probably they will tell you that you need to make your functions generic. This is possible but it complicates the code. For example:

    template<class Length>
    auto square(Length l) -> decltype(l*l){return l*l;}
    

    I use C++11 to simplify the example here (it is possible to do it in C++98), and also to show that this is becoming easier to do in C++11 (and even simpler in C++14 with decltype(auto).

    I know that this is not the type of code you had in mind but it is consistent with the design of the library. You may think, well how do I restrict this function to physical length and not something else? Well, the answer is that you don't need to this, however if you insist, in the worst case...

    template<class Length, typename std::enable_if<std::is_same<typename get_dimension<Lenght>::type, boost::units::length_dimension>::value>::type>
    auto square(Length l) -> decltype(l*l){return l*l;}
    

    (In better cases decltype will do the SFINAE job.)

    In my opinion, option 4. and possibly combined with 3. is the most elegant way ahead.


    References:

    https://www.boost.org/doc/libs/1_69_0/boost/units/get_dimension.hpp