Q: extension of units with non-unit NTTPs - what's recommended? #271
Replies: 2 comments 2 replies
-
I am happy that the library is useful in your field of experience 😃 Talking about NTTPs we use them only for Regarding your question, I am not sure what kind of extensions do you mean. I think that according to a Single Responsibility Principle we should not extend library's types with information that is not needed by the library's engine. If you want to store additional information I would suggest providing custom wrappers/aggregates that will store both the information for the dimensional analysis as well as your additional information for serialization, etc. Regarding your question about annotating member variables I think that the bests is to wait for reflection in the C++ language 😉 Until then you may look for some library-based solutions but I do not have experience in those. |
Beta Was this translation helpful? Give feedback.
-
@mpusz may I trouble you and get some help to get me jump-started? I have been experimenting on compiler-explorer but am unsure from which quantity template I need to derive and how much needs to be overridden for my use-case. Any help/hint would be welcome. Thanks in advance. The MVP I got so far: #define UNITS_REFERENCES
#define UNITS_LITERALS
#include <iostream>
#include <type_traits>
#include <units/quantity.h>
#include <units/quantity_cast.h>
#include <units/quantity_io.h>
#include <units/isq/si/length.h>
#include <units/isq/si/speed.h>
template<size_t N>
struct StringLiteral { // stand-in NTTP for extending units
char value[N+1]{};
constexpr StringLiteral(const char (&str)[N]) {
std::copy_n(str, N, value);
value[N] = '\0'; // force terminatation
}
};
template<units::Representation Rep, units::Unit U, const StringLiteral description = "">
class Annotate : public units::quantity<U, Rep> {
template<typename T> // N.B. extend this for custom classes using type-traits to query nicer class-type name
constexpr const char *getNicerTypeName() const noexcept {
if (std::is_same<T, std::byte>::value) { return "byte"; }
if (std::is_same<T, char>::value) { return "char"; }
if (std::is_same<T, short>::value) { return "short"; }
if (std::is_same<T, int>::value) { return "int"; }
if (std::is_same<T, long>::value) { return "long"; }
if (std::is_same<T, float>::value) { return "float"; }
if (std::is_same<T, double>::value) { return "double"; }
if (std::is_same<T, std::string>::value) { return "string"; }
return typeid(T).name();
}
public:
// default constructor needed or can we inherit from 'quantity'
// do I need to overload the space-ship '<=>' and other operators or can I rely on the inherited one?
//constexpr char const *getUnit() const noexcept { return U.name(); } // here: transform to base-unit and return named value
constexpr char const *getDescription() const noexcept { return description.value; }
constexpr char const *getTypeName() const noexcept { return getNicerTypeName<Rep>(); }
// friend constexpr std::ostream &operator<<(std::ostream &os, const Annotate& m) { return units::quantity::operator<<(os, m); }
};
int counter=0; // just for debugging
template<units::Representation Rep, units::Unit U, const StringLiteral description = "">
constexpr void printMetaInfo(const Annotate<Rep, U, description>& value) {
std::cout << "value" << ++counter << ": '" << value << "' type: '" << value.getTypeName() << "' unit: '" << value.getUnit() << "' description: '" << value.getDescription() << "'\n";
}
int main() {
using namespace units::isq;
using namespace units::isq::si;
using namespace units::isq::si::literals;
using namespace units::isq::si::references;
constexpr Speed auto valUnit = 110 * (km / h);
constexpr Annotate<double, metre_per_second, "custom description for velocity value"> val1(10.0 * (km /h));
constexpr Annotate<double, metre_per_second, "custom description for velocity value"> val2 = valUnit;
constexpr Annotate<float, metre, "custom description for length value"> val3 = 100.0f * (km);
printMetaInfo(val1);
printMetaInfo(val2);
printMetaInfo(val3);
constexpr Speed auto val3 = units::quantity_cast<si::speed<si::metre_per_second>>(110 * (km / h));
constexpr Speed auto val4 = units::quantity_cast<si::speed<si::metre_per_second>>(val1);
std::cout << "value3: '" << val3 << "'\n";
std::cout << "value4: '" << val4 << "'\n";
return quantity_cast<metre_per_second>(valUnit).number();
} P.S. mini side-question: have you/someone be able to use this when transpilling to WASM? The newer clang versions for x86_64 seem to work fine now. |
Beta Was this translation helpful? Give feedback.
-
Thanks for this great and very useful addition to C++. Have been working/building particle accelerators for a couple of years now and this seems to nicely tackle a common source of errors in our (and probably many other similarly critical) projects at the source! Well done and hope that it will make it into C++23! 👍
We are modernising/reimplementing our serialiser that will rely on templates using NTTP to document the (domain-object) class member's unit, description (typ. a human-readable constant string) and other meta-information (other NTTPs).
Until now, we haven't used the unit for compile-time strong type-checks but I find your library and reasoning very appealing in that respect.
How would one go about including also other NTTP-type meta information in your approach? I.e. should we:
a) make a custom wrapper around your unit templates and hope for the best that our wrapper is as light-weight and efficient as yours -- that is -- it compiles away to nothing (i.e. using
constexpr
), orb) are the already provisions in your unit templates for additional meta-data (string constants, other NTTP objects, etc.) to annotate the member variables?
This more of an open question for advice and possible feature request... Any help or suggestion would be much appreciated.
Beta Was this translation helpful? Give feedback.
All reactions