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

929 views Asked by At

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:

  • Is there another - maybe best - way to have run-time type resolution?
  • Is dynamic_cast one of them? Units conversion will not happen very often and only few data are in concern.
  • If boost::variant is a suitable solution, what are its drawbacks?
2

There are 2 answers

5
alfC On BEST ANSWER

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

0
jlandercy On

Going deeper in my problem I read two articles providing tracks for a solution:

  • Kostadin Damevski, Expressing Measurements Units in Interfaces for Scientific Component Software;
  • Lingxiao Jiang, A Practical Type System for Validating Dimensional Units Correctness of C Programs.

The first gives good ideas for the interface implementation. The second gives a complete overview of what you must cope with.

I keep in mind that boost::units is a complete and efficient way for dimension consistency at compile-time without overhead at runtime. Anyway, for runtime dimension consistency involving dimensions changes you do need a dynamic structure that boost::units does not provide. So here am I: designing a units class that will exactly fit my needs. More work to achieve, more satisfaction at the end...

About the original questions:

  • boost::variant works well (it provides the dynamic boost::units is missing) for this job. And furthermore, it can be serialized out of the box. Thus it is a effective approach. But it is adding a layer of abstraction for a simple - I am not saying trivial - task that could be done by a single class.
  • Casting is achieved by boost::variant_cast<> instead of dynamic_cast<>.
  • boost::any could be easier to implement but serialization becomes an hard way.