'Is there a way to create a scope in a template or decltype in c++17, for a unit librairy?
Does anyone know if there is any feature in c++17 that would allow you to create a scope (namespace, class or function) inside of a template or decltype() declaration? (note: lambdas don't work inside decltype until c++20.)
The reason for that would be to hide the local variables from the unit base type with a using unit::s declaration, as unit names are often short single/double letter word that are already quite commonly used in code.
The unit type is created by a macro Unit(type, unitBase) that return a Unit_t<...> and a macro unit(value, unitBase) return a value of that type.
Unit bases are UnitExpr<UnitDim> {multiplier} values named correspondingly 's' for seconds, 'm' for meters, etc..
But if we don't want to hide these commonly used names, we are forced to change their name to 's_' or '_s'. (see the reason below)
So instead of this:
Unit(int, m/s) speed = unit(10, km/h);
we have to write this:
Unit(int, m_/s_) speed = unit(10, km_/h_);
Here is the implementation of the macros:
// for type declaration
#define Unit(T, ...) Unit_t<T, typename decltype(unit_ * __VA_ARGS__)::dim, \
std::ratio<(unit_ * __VA_ARGS__).mul.num, (unit_ * __VA_ARGS__).mul.den>>
// for value creation/convertion
#define unit(val, ...) Unit_t<decltype(val), typename decltype(unit_ * __VA_ARGS__)::dim, \
std::ratio<(unit_ * __VA_ARGS__).mul.num, (unit_ * __VA_ARGS__).mul.den>> {val}
// get unit base of type
#define unitOf(unit) (UnitExpr<typename decltype(unit)::dim>{{ decltype(unit)::mul::num, decltype(unit)::mul::den }})
template<typename T, typename Dim, typename Mul>
struct Unit_t; // wrapper around a T value
template<typename Dim>
struct UnitExpr {
using dim = Dim;
Ratio mul; // runtime std::ratio
};
// use of variadic macros to allow for coma inside of {}, [] or <>
And here is what could be used to hide the name of the local variables:
#define Unit(T, ...) /*decltype or other*/ { \
using type = T; \
using unitBases::s; \
using unitBases::m; \
... -> include magic macro ? \
\
constexpr auto base = unit_* __VA_ARGS__; \
return Unit_t<...>(); or using unit = Unit_t<>; \
} /*get type back*/
// using type = T; before using unitBases:: to not hide the name of the type (ex: T in templates <> Tesla)
Is there a way to create such scope or another way to hide the names in c++17.
Here is an implementation of the code:
#include <numeric>
#include <ratio>
// unit dimension (with std::ratio dimensions)
template<typename LengthDim, typename AngleDim, typename TimeDim, ...>
struct UnitDim_t {};
template<typename UnitDim1, typename UnitDim2>
using mulUnitDim = UnitDim_t<std::ratio_add<UnitDim1::lengthDim, UnitDim2::lengthDim>, ...>;
template<typename UnitDim1, typename UnitDim2>
using divUnitDim = UnitDim_t<std::ratio_subtract<UnitDim1::lengthDim, UnitDim2::lengthDim>, ...>;
namespace UnitDim {
using Unit = UnitDim_t<0, 0, 0, ...>;
using Length = UnitDim_t<1, 0, 0, ...>;
using Angle = UnitDim_t<0, 1, 0, ...>;
using Time = UnitDim_t<0, 0, 1, ...>;
using Velocity = divUnitDim<Length, Time>; // useful for units like force in [N]
...
}
// Unit type is a wrapper around a T
template<typename T, typename Dim, typename Mul>
struct Unit_t {
using type = T;
using dim = Dim;
using mul = Mul;
T count;
constexpr Unit_t() = default;
constexpr Unit_t(T count) : count(count) {}
// conversion from different type and multiplier but not dimension
template<typename T2, typename Mul2>
constexpr Unit_t(Unit_t<T2, Dim, Mul2> u) { // count*mul = u.count*Mul2 <=> count = u.count * Mul2 / mul;
using factor = std::ratio_divide<Mul2, mul>;
count = u.count * factor::num / factor::den;
// -> add consideration to overflow for integers and constexpr optimize in general (precompute factor for floating point T)
}
};
template<typename T1, typename Dim1, typename Mul1, typename T2, typename Dim2, typename Mul2>
constexpr auto operator*(Unit_t<T1, Dim1, Mul1> u1, Unit_t<T2, Dim2, Mul2> u2) {
return Unit_t<decltype(u1.count* u2.count), mulUnitDim<Dim1, Dim2>, std::ratio_multiply<Mul1, Mul2>> { u1.count * u2.count };
}
// same for T * Unit and division
// naive implementation of a runtime std::ratio
struct Ratio {
uintmax_t num, den = 1;
// operators * and / for Ratio
};
// unit expression for the base units
template<typename Dim /*, typename PiExp*/> // add exponant for pi and support for sqrt of ratios (no floating point in template in c++17)
struct UnitExpr {
using dim = Dim;
Ratio mul; // necesary because there is no constrexpr function paramerters
// operators * and / for UnitExpr
};
//namespace unitBases { // maybe namespace
constexpr UnitExpr<typename UnitDim::Unit> unit_ = {{1}};
constexpr UnitExpr<typename UnitDim::Length> m_ = {{1}};
constexpr UnitExpr<typename UnitDim::Length> km_ = {{1000}};
constexpr UnitExpr<typename UnitDim::Angle> rad_ = {{1}};
constexpr UnitExpr<typename UnitDim::Time> s_ = {{1}};
constexpr UnitExpr<typename UnitDim::Time> min_ = {{60}};
constexpr UnitExpr<typename UnitDim::Time> h_ = {{3600}};
//}
Then we can do this:
Unit(int, m_) someLength = unit(10, km_); // count = 10000
Unit(float, 1/10*m_/s_) speed = someLength / unit(4.2, h_/15); // count = 99.2064 (9.92 m/s)
// note that we first multiply by unit_ in macro: "(unit_ * 1/10*m_/s_)" so that mul don't become zero, or another reduced value.
Unit(uint16_t, unitOf(someLength) / 100 / rad_) angularDist = 2 * speed / unit(M_PI/2.0, rad_) * unit(1, s_); // count = 1263
In c++20, decltype accept lambdas, so we can just change the macro to:
#define Unit(T, ...) decltype([](){ \
using type = T; \
using unitBases::s; \
using unitBases::m; \
... -> include magic macro ? \
\
constexpr auto base = unit_* __VA_ARGS__; \
return Unit_t<type, typename base::dim, \
std::ratio<base.mul.num, base.mul.den>>; \
}())
More generally speaking, if c++20 is allowed, we can just simplify:
// use struct values instead of types
template<Ratio LengthDim, Ratio AngleDim, Ratio TimeDim, ...>
struct UnitDim_t {};
template<typename Dim, Ratio Mul>
struct UnitExpr {};
template<typename T, UnitExpr UnitBase>
struct Unit_t;
// no macro now viable, but with the name issue and another problem:
Unit_t<int, 1/10*m_/s_> speed; // Error: multiplier is 0 because of integer division
Unit_t<int, 5/2*m_/s_> speed; // OK: BUT STILL NOT CORRECT!
Unit_t<int, unit_*5/2*m_/s_> speed; // OK
And the macro becomes:
#define Unit(T, ...) Unit_t<T, decltype([](){ \
using unitBases::s; \
using unitBases::m; \
... -> include magic macro ? \
\
return (unit_* __VA_ARGS__); \
}())>
So is there any way to do the same with c++17?
Oh, and here is some macro magic madness for those who swing that way:
#define CONCAT_HELPER(a, b) a##b
#define CONCAT(a, b) CONCAT_HELPER(a, b)
#define EXPEND(x) x // because MSVC consider __VA_ARG__ as one argument for some reason
#define COUNT_ARGS_HELPER(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, n,...) n
#define COUNT_ARGS(...) EXPEND(COUNT_ARGS_HELPER(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
// add more numbers if needed
#define IMPORT_UNITS2(unitDim, unit) using unitBases::unitDim::unit;
#define IMPORT_UNITS3(unitDim, unit, ...) using unitBases::unitDim::unit; EXPEND(IMPORT_UNITS2(unitDim, __VA_ARGS__))
#define IMPORT_UNITS4(unitDim, unit, ...) using unitBases::unitDim::unit; EXPEND(IMPORT_UNITS3(unitDim, __VA_ARGS__))
#define IMPORT_UNITS5(unitDim, unit, ...) using unitBases::unitDim::unit; EXPEND(IMPORT_UNITS4(unitDim, __VA_ARGS__))
#define IMPORT_UNITS6(unitDim, unit, ...) using unitBases::unitDim::unit; EXPEND(IMPORT_UNITS5(unitDim, __VA_ARGS__))
#define IMPORT_UNITS7(unitDim, unit, ...) using unitBases::unitDim::unit; EXPEND(IMPORT_UNITS6(unitDim, __VA_ARGS__))
#define IMPORT_UNITS8(unitDim, unit, ...) using unitBases::unitDim::unit; EXPEND(IMPORT_UNITS7(unitDim, __VA_ARGS__))
#define IMPORT_UNITS9(unitDim, unit, ...) using unitBases::unitDim::unit; EXPEND(IMPORT_UNITS8(unitDim, __VA_ARGS__))
#define IMPORT_UNITS10(unitDim, unit, ...) using unitBases::unitDim::unit; EXPEND(IMPORT_UNITS9(unitDim, __VA_ARGS__))
#define IMPORT_UNITS(...) CONCAT(IMPORT_UNITS, COUNT_ARGS(__VA_ARGS__)) (__VA_ARGS__)
#define IMPORT_UNIT_DIMS1(unitDim) IMPORT_UNITS(unitDim, CONCAT(unitDim, _UNITS))
#define IMPORT_UNIT_DIMS2(unitDim, ...) IMPORT_UNITS(unitDim, CONCAT(unitDim, _UNITS)) EXPEND(IMPORT_UNIT_DIMS1(__VA_ARGS__))
#define IMPORT_UNIT_DIMS3(unitDim, ...) IMPORT_UNITS(unitDim, CONCAT(unitDim, _UNITS)) EXPEND(IMPORT_UNIT_DIMS2(__VA_ARGS__))
#define IMPORT_UNIT_DIMS4(unitDim, ...) IMPORT_UNITS(unitDim, CONCAT(unitDim, _UNITS)) EXPEND(IMPORT_UNIT_DIMS3(__VA_ARGS__))
#define IMPORT_UNIT_DIMS5(unitDim, ...) IMPORT_UNITS(unitDim, CONCAT(unitDim, _UNITS)) EXPEND(IMPORT_UNIT_DIMS4(__VA_ARGS__))
#define IMPORT_UNIT_DIMS6(unitDim, ...) IMPORT_UNITS(unitDim, CONCAT(unitDim, _UNITS)) EXPEND(IMPORT_UNIT_DIMS5(__VA_ARGS__))
#define IMPORT_UNIT_DIMS7(unitDim, ...) IMPORT_UNITS(unitDim, CONCAT(unitDim, _UNITS)) EXPEND(IMPORT_UNIT_DIMS6(__VA_ARGS__))
#define IMPORT_UNIT_DIMS8(unitDim, ...) IMPORT_UNITS(unitDim, CONCAT(unitDim, _UNITS)) EXPEND(IMPORT_UNIT_DIMS7(__VA_ARGS__))
#define IMPORT_UNIT_DIMS(...) CONCAT(IMPORT_UNIT_DIMS, COUNT_ARGS(__VA_ARGS__)) (__VA_ARGS__)
#define IMPORT_UNIT_LIBS1(unitLib) IMPORT_UNIT_DIMS(CONCAT(unitLib, _DIMS))
#define IMPORT_UNIT_LIBS2(unitLib, ...) IMPORT_UNIT_DIMS(CONCAT(unitLib, _DIMS)) IDENTITY(IMPORT_UNIT_LIBS1(__VA_ARGS__))
#define IMPORT_UNIT_LIBS3(unitLib, ...) IMPORT_UNIT_DIMS(CONCAT(unitLib, _DIMS)) IDENTITY(IMPORT_UNIT_LIBS2(__VA_ARGS__))
#define IMPORT_UNIT_LIBS4(unitLib, ...) IMPORT_UNIT_DIMS(CONCAT(unitLib, _DIMS)) IDENTITY(IMPORT_UNIT_LIBS3(__VA_ARGS__))
#define IMPORT_UNIT_LIBS5(unitLib, ...) IMPORT_UNIT_DIMS(CONCAT(unitLib, _DIMS)) IDENTITY(IMPORT_UNIT_LIBS4(__VA_ARGS__))
#define IMPORT_UNIT_LIBS6(unitLib, ...) IMPORT_UNIT_DIMS(CONCAT(unitLib, _DIMS)) IDENTITY(IMPORT_UNIT_LIBS5(__VA_ARGS__))
#define IMPORT_UNIT_LIBS7(unitLib, ...) IMPORT_UNIT_DIMS(CONCAT(unitLib, _DIMS)) IDENTITY(IMPORT_UNIT_LIBS6(__VA_ARGS__))
#define IMPORT_UNIT_LIBS8(unitLib, ...) IMPORT_UNIT_DIMS(CONCAT(unitLib, _DIMS)) IDENTITY(IMPORT_UNIT_LIBS7(__VA_ARGS__))
#define IMPORT_UNIT_LIBS(...) CONCAT(IMPORT_UNIT_LIBS, COUNT_ARGS(__VA_ARGS__)) (__VA_ARGS__)
// main macro used instead of "using unitBase::unitDim1::unit1; ..."
#define IMPORT_ALL_UNITS IMPORT_UNIT_LIBS(UNIT_LIBS)
// Why don't we have "using unitBase::*::*" instead of this madness?
#define STD_UNITS_DIMS Length, Angle, Time, Mass //, ...
#define Length_UNITS m, km, dm, cm, mm, um
#define Angle_UNITS rad, deg
#define Time_UNITS s, min, h, ms, us, ns
#define Mass_UNITS g, kg, t, mg
//...
//===== unitConfig.h =====
// add your own unit libraries
#define UNIT_LIBS STD_UNITS, DATA_UNITS
// add _DIMS for the unit dimensions
// STD_UNITS -> #define STD_UNITS_DIMS Length, ... (Dims are namespace names inside of namespace unitBases)
// add _UNITS for the units
// Length -> #define Length_UNITS m, km, ... (units are names of units inside of namespace unitBases::Dim)
//ex:
#define DATA_UNITS_DIMS Data
#define Data_UNITS bit, B, kB, MB, GB, TB, KiB, MiB, GiB
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
