From 6bf09aa646502ab268d17125d517e2045fb05cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johel=20Ernesto=20Guerrero=20Pe=C3=B1a?= Date: Mon, 4 Jan 2021 18:36:26 -0400 Subject: [PATCH] feat: quantity_kind and quantity_point_kind --- CHANGELOG.md | 0 cmake/warnings.cmake | 2 +- docs/CHANGELOG.md | 2 + docs/CMakeLists.txt | 1 + docs/_static/img/concepts.png | Bin 25727 -> 58847 bytes docs/examples/glide_computer.rst | 20 +- docs/framework/basic_concepts.rst | 26 +- docs/framework/conversions_and_casting.rst | 16 +- docs/framework/dimensions.rst | 26 +- docs/framework/quantity_kinds.rst | 72 ++ docs/framework/quantity_points.rst | 2 +- docs/framework/text_output.rst | 5 +- docs/usage.rst | 2 +- docs/use_cases/legacy_interfaces.rst | 8 +- example/glide_computer.cpp | 189 ++--- src/include/units/bits/basic_concepts.h | 80 ++- src/include/units/bits/common_quantity.h | 50 +- src/include/units/bits/equivalent.h | 19 +- src/include/units/bits/quantity_of.h | 37 + src/include/units/kind.h | 64 ++ src/include/units/quantity_cast.h | 132 +++- src/include/units/quantity_kind.h | 351 +++++++++ src/include/units/quantity_point.h | 18 +- src/include/units/quantity_point_kind.h | 195 +++++ test/unit_test/static/CMakeLists.txt | 5 +- test/unit_test/static/kind_test.cpp | 203 ++++++ test/unit_test/static/quantity_kind_test.cpp | 675 ++++++++++++++++++ .../static/quantity_point_kind_test.cpp | 654 +++++++++++++++++ test/unit_test/static/quantity_point_test.cpp | 14 +- test/unit_test/static/quantity_test.cpp | 2 +- test/unit_test/static/test_tools.h | 87 +++ test/unit_test/static/unit_test.cpp | 2 + 32 files changed, 2757 insertions(+), 202 deletions(-) delete mode 100644 CHANGELOG.md create mode 100644 docs/framework/quantity_kinds.rst create mode 100644 src/include/units/kind.h create mode 100644 src/include/units/quantity_kind.h create mode 100644 src/include/units/quantity_point_kind.h create mode 100644 test/unit_test/static/kind_test.cpp create mode 100644 test/unit_test/static/quantity_kind_test.cpp create mode 100644 test/unit_test/static/quantity_point_kind_test.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index e69de29b..00000000 diff --git a/cmake/warnings.cmake b/cmake/warnings.cmake index 03c8fac3..ce7feda0 100644 --- a/cmake/warnings.cmake +++ b/cmake/warnings.cmake @@ -39,7 +39,7 @@ function(set_warnings target scope) set(MSVC_WARNINGS /W4 # Baseline reasonable warnings /w14062 # enumerator 'identifier' in a switch of enum 'enumeration' is not handled - /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data + # /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data /w14263 # 'function': member function does not override any base class virtual member function /w14265 # 'classname': class has virtual functions, but destructor is not diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3e8b05ee..d3d7b985 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,8 +3,10 @@ - **0.7.0 WIP** - (!) refactor: `ScalableNumber` renamed to `QuantityValue` - (!) refactor: output stream operators moved to the `units/quantity_io.h` header file + - refactor: `quantity_point` (partially) updated to reflect latest changes to `quantity` - refactor: basic concepts, `quantity` and `quantity_cast` refactored - refactor: `abs()` definition refactored to be more explicit about the return type + - feat: quantity (point) kind support added (thanks [@johelegp](https://github.com/johelegp)) - feat: unit constants support added (thanks [@johelegp](https://github.com/johelegp)) - feat: interoperability with `std::chrono::duration` and other units libraries - feat: CTAD for dimensionless quantity added diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 7e40163c..7769a311 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -70,6 +70,7 @@ set(unitsSphinxDocs "${CMAKE_CURRENT_SOURCE_DIR}/framework/dimensions.rst" "${CMAKE_CURRENT_SOURCE_DIR}/framework/quantities.rst" "${CMAKE_CURRENT_SOURCE_DIR}/framework/quantity_points.rst" + "${CMAKE_CURRENT_SOURCE_DIR}/framework/quantity_kinds.rst" "${CMAKE_CURRENT_SOURCE_DIR}/framework/text_output.rst" "${CMAKE_CURRENT_SOURCE_DIR}/framework/units.rst" diff --git a/docs/_static/img/concepts.png b/docs/_static/img/concepts.png index 3bf55b5b0885901d69883f8c87aed347a1f31bea..31b56e8edd9fff933278b89dc4da02b3e9fae15c 100644 GIT binary patch literal 58847 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 ^c{ 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 b0lqKd8vUQv3uw-}3{vmu>gTe~DWM4fqoqcF literal 25727 zcmeAS@N?(olHy`uVBq!ia0y~yU_8vgz{JnN#=yY9@3OUrfkA=6)5S5QBJS;6&KV*{ zfBwHOcR*E6Mo!_NHJiwd$g;Gl6|Nx@Dn4FUQ0oxNSJ3*v%p~Ee#~{Jq8-ar_U*INa?Fo6jyBIy8M56)yj>_wpH({URJ%U`j_?j$?BV8zg~-;zkS)h zzhR-@HCp&N8W=#RWln?8r+pvXwmJ?5u(Ae*BLc|` zEDj7T4hmmb6xtTbGciDPFc=<@0I6pb;IL)vNPN-GzyLAXfya;yq@IDr$)6!f;+G=G zuOLeVoO>7r7#L5p^u4;ZRa?_KI%?g6P{*w&uWb)s8}&5wbB1{Zi$dGMZb=(9Mt_bY zk1t9Etz50%`~3O~_s*187yhhT6n2Bdf#i&3*^1pQm2++NbYY_|j`ZtMYoon37)HHu!nYuw&tS zC#H6X1M^vSNVuK0zPLp*`R_{=!N17|lTGRmJ{EAk?{;E-pCiv-N%0C+rhUo`hF@Ym z9!zLvQE1~k)S@Y%k;)`+IH*tW7aJrbHn=b(WpD~}u})z)Vz8jG`L;qp4wJy)Bw_dJ zqbdxBI%>IFSeY2r7z}v|K0Mf^$=I<_Q%Qt@Q6Paup-qB)sSiU^#Hov}_Z<#6G6^`d zCmx#ep2LBsR$Rz}h3TJ4LO753{vAD&R)<~wurNFz@@C#lzuddubYsomiS7C`sqVy>_S~M|;+HOlinf)0oU>3eFy^{4gW-`K42%LBTn?<0FuIsD z|Fqt_>8bNOZ|h!8ySzfM`Q-xU+tFFG#gj^(-`m{a)+^(q8~tW!w0Kemw#2&ee< zN!WUR{@T&kewkO;?IoAekqbc zmc^!Q_HFs@d;Q+#y)Xvr<4Ec-WMER5z|Ux%bRup_?fF`6>D2s~KS{B>Gn$TlTe6?;7q_Xtw(%LWqyHRuK6mQ9QZNRm+zl=V`Xr=e=Os!$ zzJJ{B+Bfc-4<*%RowxfR^lU<}&a^kbqP4TP*1E5*?iFzMe(0i@(k8*Iz`*oVV1riM zrUjk3Cnc4IeZM_CJ@1oe@W)+&+cdwkCe83K31pwCc#MIgp+S|&uVZ1&p^!Qsr*r>~ zy!MeW&$)BuMTypt3qd~uPP8x_5pZT_Q>+TB<*=p@N#nQYEAv+w?i(#x-6zy2x<<|<7!(-i#W75ei3Mys&;JJUHIU6Cv{K0D{m-yLh% zKO8-5_-Cf0;qS@v+n#^>cV&CIpQFfh&(NT0+F8HUgQooLTp=sC)#UB=Np?09BmTx; zF471HVo_*Q74<7&V_75Eaa>DIG3a-c_T1Q0t0bN!-|np6uuQS4>o67FC)iXliUMrm$#ne<>zo%J zk{WnF_%uLx7w*r<-71Tbm{hx)_Fl9UthW`T6gaBzXfj} z2{cHEwHrqnmUUz8MSePM;e5 z@}Z0GD*NBj0Y?j-o(etvXzJ7J6Bn86c^dskqh#r-Ro0@RFCDqQDl7`Kn)A=Q>sN@1 zZ}+`*E322jXmn|IRb(*K;Yu@MVscen5E44|yWjGgpO#SoA>9)Sj zvf}4^U!R^L$K_fz!OKVH>!v4XT}}mSU0n2j|NXeVd3Sf^cMCaf&6M;qUf=!hTA{1Eg6^(~{QOE(J1j@owktfWN6~d}3o&p0 zwZA!@pDTG1Fz;{Cb}en~dfA0@%O_s?AjJ4~vB~dJrHMCEo>Zl#PG9A*#FO7Ow0ech z@)uKsJ!9n*tFqj8o$Ov6wl?aiGEc!#CdQODF@=R392LF`R;|kZUJ~_o?SxyG6aMG7 zJB6%WO9kArTo*Xtx+~& za~)lr*LEztF~4HZ>3or`p8TPK($j*!ewVx->KVC2F z;j{nqVbe6-`m3|AoOpRfp`q$`ef)=~{;oeOGZjylUW){+3RR8$`?%kF_4PH8pXaJR)(GKYnpm-? zNq_04p1|sTfqzzLEfsaG?GkyqbFOv0<$(~M=NH`am;{`A1Oz@za0m@`UApSh%-!D> zmCXMi+;J~NbgR|eWv^z<+7)hFBJO)zp&;b?I`^fsm;4NRb+RfnbWwQ6mFFU^EB#c0 zJf)unUP)vUaBgorB*QU7u;aM4UufvB!|Q^(o?1P9?YVSm*P^i1T%nH_<{K~iw_mQi z)Gj7)cGmBn-F|;x{jEBd6?8TI(vpztSC&lswLg66Q^g}2wmj;*_hZ6=LRsr~Umwfu z{+d@&=bL(d{mZ>ypBT5=GOX9QYF(yrb?erx(zQ24cRXQeT-IVF_Bqe}tYRxRcb@w!eeY_i1{q+teG1GJ7xY`s#8@Y3ik`zOw?p-p;>X`{1)3>m}QcM1{j_ z%l~>P$n}>k-f}BA^hUJL3zt~=&?@IZDY*uJm8|&h$@UK>m$R(W((3awKdG~H-je5? zPFuD-6r45ps;TMf@T%vznOk`bb=FoaGIv!A2#u_pCACoQ$h}<6Jy%@{9=pH&Y2P}{ ztxV%;{`G77f>XpV#yD`A?1{0x>m0bUebrvp=hLsBNZqyMf>vl~T#w6?S5vjaH`X32 zaoGKO=WPM!^Ue(HO)d>@zrVS2b4~bRmZh8z8m<2Q?-%*@&wtyWpTVU{8?SvhtLFI1 zy5?)2gN@w%aL@mD7C$`ZU47wyf^5Fo1h4L!tDG+{`+7Bh@7H(N?^Vj|nWhMCS?U=z zu3D9GykwSctZP-k;cBh#R|*qLo!KN-{`>V>*W3Qri^XqU%Y@Iiwm971S3CR9_x-P> z@69|_m+5@g9NIEB(z% z+e4Q(^z08`?8LQpk%?~V`3>7LHkN%aV#&R^KG*Kbe);$8vHcMP`K%Sf5lkN|D&vEuUhH)V2%K)JBddA!WXdc1#dbhnYvEPC(yK|NwCm5~b7E)Mc5xgrxbU#~a~a1Qp+oL1R$rH_+HUIU z{;q!Ywz9pl(Hh)LOPwCP=<4FLo_(ox$*ORnz}G(eMZWGa;8Z%YA;it(*Tl&UIxSo) z`B!!oN!MvIFtf8rab&dTD`l#6ykVa=JENXwqJk68M_-LfrZ+Ek&I$|XDBtr}xN}## z_rAw36gZwZD#-O8yRm+mK%llSzp~eM1DifZ!!yYn7IvOJ(xb|KDz@Z@<+pvCCDg%vL?%>Q?V^Q=!V854(LHPd<5t<^0?x zW(5HSro>--jy%jJ1`k)+G9~^}x~<%NNg<)vVa7gZ0q5*wrlm|C3O?~q7~0tN*c-s2 zhZ%T0cFd4(G^m~US-B-jVZv;%uyc*H*EEM2`%b7zyOf*>hL~$Bo0W0L7i8}4lY-k0 zdn@?dSL{fvI9_@3yNyr$lS%w)Rxdak4QksJ+UD^Xnndw{l!`a2-{Jt7->%U1Q2PGD zmxVL-omj}JWfc_yGWDj=;fghn>Xg1NTI9yu+sMGt;P8S&!(VOE*5?&>b2}FLvAAf& zv2(m_2Fa`x4!HDka@e}4y=P9>Iq~=&*|6x3hyLT{ZH^2q4hvE^uUuPu^%t9=PF{zu z{^YK54)JgH)7ief503G_D88IoSy z-O(UXbzDvtY?T;e$HH=Erlc3L)mwNV9qjo%j2#R8nHbH%9q^e8%aa)p(kFOojo1|0 z>^NEOfIIFfZTEUXM!dOb1hQGd_{&L&28mxP0!!Z<19cwv%f(K)dQ{m;&d=OO!c{l= z&D5X2)uqiYP3{)EeBiX+f=!$5*0iR4?B>;HdRdxP9{-ay-K(3y$oc&vPQx#KwHi_D z*8lHZc7D(7q}bgloSv)lc<&nJn%yH!7X2wnw!Mm-gZ-bQGb53DwBY-_ly`#7Q+%&?VchgpOy$Vi>xCL z+$0sEt9;f)t?d;1eP-@=fw^~gi*L={Jb#++yY2VwW?7WIDcJGtefhODr#red=ETg2 zs0@s7U6?xaMaRX-Mob#NRev3tmNE0M{{BBvpWlAg-*-mme%|`^*N^-?bJ_2rQLN?X zr~jwbiq;-{!N91@B;fr17~7Ex*54LpM2bmny!^;i&B*3=QtFHcAuiULkCtt}a_Y#d z1K-}7I3_ujpReWK{7vEM{*Bwra_&qK^tLseW0kDeD`U3d;y#7VU$32tTDvZ;{z=^( zqf#^8J$vfi{ygQe5NUK*|E$Ddc!qnn@>GSkzJwM*Wu~1i4k8+X1t|-YPET2MprFv^ z@s`QP-m5+_%0AUPI$|5y9y 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]<-[derived_dimension] ] + [Quantity| + [quantity] + ] + [QuantityPoint| [quantity_point] ] + [QuantityKind| + [quantity_kind] + ] + + [QuantityPointKind| + [quantity_point_kind] + ] + [Unit]<-[Dimension] [Dimension]<-[Quantity] [Unit]<-[Quantity] [Quantity]<-[QuantityPoint] + [Kind]<-[QuantityKind] + [Dimension]<-[Kind] + [Quantity]<-[QuantityKind] + + [PointKind]<-[QuantityPointKind] + [Kind]<-[PointKind] + [QuantityKind]-<[QuantityPointKind] + `Unit` is a basic building block of the library. Every dimension works with a concrete hierarchy of units. Such hierarchy defines a reference unit and often a few scaled versions of it. @@ -40,3 +60,7 @@ derived dimensions. specific representation. `QuantityPoint` is an absolute `Quantity` with respect to some origin. + +`QuantityKind` is a `Quantity` with more specific usage. + +`QuantityPointKind` is an absolute `QuantityKind` with respect to some origin. diff --git a/docs/framework/conversions_and_casting.rst b/docs/framework/conversions_and_casting.rst index 52fe490f..7f6deaac 100644 --- a/docs/framework/conversions_and_casting.rst +++ b/docs/framework/conversions_and_casting.rst @@ -56,7 +56,8 @@ Explicit -------- Explicit conversions are available with -the `quantity_cast` and `quantity_point_cast` function templates. +the `quantity_cast`, `quantity_point_cast`, +`quantity_kind_cast`, and `quantity_point_kind_cast` function templates. They are especially useful to force a truncating conversion across quantities of the same dimension for integral representation types and ratios that may cause precision loss:: @@ -99,6 +100,13 @@ or a specific target `quantity_point`:: std::cout << "Point: " << quantity_point_cast(d) << '\n'; +For a single explicit template argument, +`quantity_point_kind_cast` is a superset of `quantity_kind_cast`, +which is also a superset of `quantity_cast`. +For the (dimension, unit) pair of explicit arguments case of `quantity_cast`, +`quantity_point_kind_cast` and `quantity_kind_cast` +accept a `PointKind` and `Kind` instead of a `Dimension`, respectively. + .. seealso:: For more information on conversion and casting and on how to extend the above "integral" @@ -115,14 +123,14 @@ represent numbers it would be highly uncomfortable to every time type:: const auto d1 = 10_q_km; const auto d2 = 3_q_km; - if(d1 / d2 > dimensionless) { + if(d1 / d2 > dimensionless(2)) { // ... } or:: const auto fill_time_left = (box.height / box.fill_level(measured_mass) - - dimensionless) * fill_time; + dimensionless(1)) * fill_time; This is why it was decided to allow the ``dimensionless`` quantity of any representation type to be implicitly constructible from this representation type. @@ -147,7 +155,7 @@ could be ambiguous. For example:: return d1 / d2 + 1; } -As long as we can reason about what such code means for ``foo(10_q_km, 2_q_km)`` it is not that obvious +As long as we can reason about what such code means for ``foo(10_q_km, 2_q_km)`` it is not that obvious at all in the case of ``foo(10_q_cm, 2_q_ft)``. To make such code to compile for every case we have to either change the type of the resulting unit to the one having ``ratio(1)`` (:term:`coherent derived unit`):: diff --git a/docs/framework/dimensions.rst b/docs/framework/dimensions.rst index 98004b52..279f83a6 100644 --- a/docs/framework/dimensions.rst +++ b/docs/framework/dimensions.rst @@ -50,7 +50,7 @@ probably will always end up in a quantity of a yet another dimension: However, please note that there is an exception from the above rule. In case we divide the same dimensions, or multiply by the inverted -dimension, than we will end up with just a scalable number type: +dimension, than we will end up with just a dimensionless quantity: .. code-block:: :emphasize-lines: 4-5 @@ -58,8 +58,23 @@ dimension, than we will end up with just a scalable number type: Time auto dur1 = 10_q_s; Time auto dur2 = 2_q_s; Frequency auto fr1 = 5_q_Hz; - QuantityValue auto v1 = dur1 / dur2; // 5 - QuantityValue auto v2 = dur1 * fr1; // 50 + Dimensionless auto v1 = dur1 / dur2; // quantity(5) + Dimensionless auto v2 = dur1 * fr1; // quantity(50) + +Quantity kinds behave the same as quantities for addition, substraction, and +multiplication with, and division by a :term:`scalable number`. + +Multiplication and divison with a quantity +(but not a quantity kind) is allowed. +The result is a quantity kind with the appropiate dimension +and related to the original quantity kind. + + struct height : kind {}; + struct rate_of_climb : derived_kind {}; + + quantity_kind h(height{}, 100 * m); + quantity_point_kind rate = h / (25 * s); + // `quantity_point_kind(4 * m / s)` Quantity points have a more restricted set of operations. Quantity points can't be added together, @@ -81,7 +96,7 @@ The result will always be a quantity point of the same dimension: .. code-block:: :emphasize-lines: 3 - + Length auto dist1 = 2_q_m; Length auto dist2 = 1_q_m; auto res1 = dist1 - quantity_point{dist2}; // ERROR @@ -98,6 +113,9 @@ The result is a relative quantity of the same dimension: That's it! You can't multiply nor divide quantity points with anything else. +The same restrictions of a quantity point with respect to its quantity +apply to a quantity point kind with respect to its quantity kind. + Base Dimensions --------------- diff --git a/docs/framework/quantity_kinds.rst b/docs/framework/quantity_kinds.rst new file mode 100644 index 00000000..3ebb8a43 --- /dev/null +++ b/docs/framework/quantity_kinds.rst @@ -0,0 +1,72 @@ +.. namespace:: units + +Quantity Kinds +============== + +A quantity kind is a quantity of more specific usage. +It is represented in the library with a `quantity_kind` class template. + + +.. _quantity-point-construction: + +Kind creation +------------- + +We need a `kind` to represent the more specific usage of a quantity. + + struct radius : kind {}; + +Quantities of kind `radius` represent a radius. +They are clearly distinct from more generic usages of length quantities. + + +Construction +------------ + +To create the quantity kind object from a `quantity` we just have to pass +the value to the `quantity_kind` class template constructor:: + + quantity_kind d(123 * km); + +.. note:: + + As the constructor without the kind argument is explicit, + the quantity object can be created from a quantity only via + `direct initialization `_. + This is why the code below using + `copy initialization `_ + **does not compile**:: + + quantity_kind d = 123 * km; // ERROR + + +Differences to quantity +----------------------- + +Unlike `quantity`, the library provides: + +- no kinds, such as ``radius`` or ``width``, therefore + * no UDLs or unit constants, + * no kind-specific concepts, such as ``Radius``, + (there's the generic `QuantityKind` and kind-specifiable `QuantityKindOf`), +- a slightly different set of operations on quantity kinds + (see the :ref:`Dimensions` chapter) + +Quantity point kind +------------------- + +A quantity point kind is the analogous of a quantity point for quantity kinds. +(see the :ref:`Quantity Points` chapter). + +They are represented in the library with a `quantity_point_kind` class template. + +First, we need a `point_kind` for a `kind`. + + struct width : kind {}; + struct x_coordinate : point_kind {}; + +Now x coordinates can be constructed: + + quantity_point_kind auto x_pos(123 * m); + // `QuantityPointKindOf` with `x_pos.relative()` + // equal to `quantity_kind(123 * m)` diff --git a/docs/framework/quantity_points.rst b/docs/framework/quantity_points.rst index 87fea78c..4652da66 100644 --- a/docs/framework/quantity_points.rst +++ b/docs/framework/quantity_points.rst @@ -40,4 +40,4 @@ Unlike `quantity`, the library provides: - no dimension-specific concepts, such as ``LengthPoint`` (there's the dimension-agnostic `QuantityPoint`), - a more limited set of operations on quantity points - (see the :ref:`Conversions and Casting` chapter) + (see the :ref:`Dimensions` chapter) diff --git a/docs/framework/text_output.rst b/docs/framework/text_output.rst index 9172d21b..a714e24f 100644 --- a/docs/framework/text_output.rst +++ b/docs/framework/text_output.rst @@ -8,13 +8,14 @@ also tries really hard to print any quantity in the most user friendly way. .. note:: - The library provides no text output for quantity points. + The library provides no text output for + quantity points or quantity (point) kinds. Output Streams -------------- .. tip:: - + The streaming support is provided via the ```` header file. diff --git a/docs/usage.rst b/docs/usage.rst index 6973adec..1cc3c9a6 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -18,7 +18,7 @@ This repository contains three independent CMake-based projects: but until then it depends on: - `{fmt} `_ to provide text formatting of quantities. - - `gsl-lite `_ to verify runtime contracts with ``Expects`` macro. + - `gsl-lite `_ to verify runtime contracts with the ``gsl_Expects`` macro. - *.* diff --git a/docs/use_cases/legacy_interfaces.rst b/docs/use_cases/legacy_interfaces.rst index 1f099753..4b6834b4 100644 --- a/docs/use_cases/legacy_interfaces.rst +++ b/docs/use_cases/legacy_interfaces.rst @@ -4,8 +4,12 @@ Working with Legacy Interfaces ============================== In case we are working with a legacy/unsafe interface we may be forced to -extract the :term:`value of a quantity` with :func:`quantity::count()` or to -extract the value of a `quantity_point` with :func:`quantity_point::relative()` +extract the :term:`value of a quantity` with :func:`quantity::count()` +(in addition +to the quantity of a `quantity_point` with :func:`quantity_point::relative()`, +or the quantity of a `quantity_kind` with :func:`quantity_kind::common()`, +or the quantity kind of a `quantity_point_kind` +with :func:`quantity_point_kind::relative()`) and pass it to the library's output: .. code-block:: diff --git a/example/glide_computer.cpp b/example/glide_computer.cpp index dc17d878..d3576ac9 100644 --- a/example/glide_computer.cpp +++ b/example/glide_computer.cpp @@ -24,130 +24,17 @@ #include #include #include -#include +#include #include #include #include -// horizontal/vertical vector -namespace { - -using namespace units; - -enum class direction { - horizontal, - vertical -}; - -template - requires Quantity || QuantityPoint -class vector { -public: - using value_type = Q; - using magnitude_type = Q; - static constexpr direction dir = D; - - vector() = default; - explicit constexpr vector(const Q& m): magnitude_(m) {} - - template - requires QuantityPoint && std::constructible_from - explicit constexpr vector(const QQ& q) : magnitude_(q) {} - - constexpr Q magnitude() const { return magnitude_; } - - [[nodiscard]] constexpr vector operator-() const - requires requires { -magnitude(); } - { - return vector(-magnitude()); - } - - template - constexpr vector& operator-=(const vector& v) - requires requires(Q q) { q -= v.magnitude(); } - { - magnitude_ -= v.magnitude(); - return *this; - } - - template - [[nodiscard]] friend constexpr auto operator+(const vector& lhs, const vector& rhs) - requires requires { lhs.magnitude() + rhs.magnitude(); } - { - using ret_type = decltype(lhs.magnitude() + rhs.magnitude()); - return vector(lhs.magnitude() + rhs.magnitude()); - } - - template - [[nodiscard]] friend constexpr auto operator-(const vector& lhs, const vector& rhs) - requires requires { lhs.magnitude() - rhs.magnitude(); } - { - using ret_type = decltype(lhs.magnitude() - rhs.magnitude()); - return vector(lhs.magnitude() - rhs.magnitude()); - } - - template - requires (QuantityValue || Dimensionless) - [[nodiscard]] friend constexpr auto operator*(const vector& lhs, const V& value) - requires requires { lhs.magnitude() * value; } - { - return vector(lhs.magnitude() * value); - } - - template - requires (QuantityValue || Dimensionless) - [[nodiscard]] friend constexpr auto operator*(const V& value, const vector& rhs) - requires requires { value * rhs.magnitude(); } - { - return vector(value * rhs.magnitude()); - } - - template - [[nodiscard]] friend constexpr auto operator/(const vector& lhs, const vector& rhs) - requires requires { lhs.magnitude() / rhs.magnitude(); } - { - return lhs.magnitude() / rhs.magnitude(); - } - - template - [[nodiscard]] friend constexpr auto operator<=>(const vector& lhs, const vector& rhs) - requires requires { lhs.magnitude() <=> rhs.magnitude(); } - { - return lhs.magnitude() <=> rhs.magnitude(); - } - - template - [[nodiscard]] friend constexpr bool operator==(const vector& lhs, const vector& rhs) - requires requires { lhs.magnitude() == rhs.magnitude(); } - { - return lhs.magnitude() == rhs.magnitude(); - } - - template - requires Quantity - friend std::basic_ostream& operator<<(std::basic_ostream& os, const vector& v) - { - return os << v.magnitude(); - } - -private: - Q magnitude_{}; -}; - - -template -inline constexpr bool is_vector = false; -template -inline constexpr bool is_vector> = true; - -} // namespace - -template -struct fmt::formatter> : formatter { +template +struct fmt::formatter : formatter { template - auto format(const vector& v, FormatContext& ctx) + auto format(const QK& v, FormatContext& ctx) { - return formatter::format(v.magnitude(), ctx); + return formatter::format(v.common(), ctx); } }; @@ -155,20 +42,27 @@ struct fmt::formatter> : formatter { namespace { using namespace units::physical; +using namespace units; -using distance = vector, direction::horizontal>; -using height = vector, direction::vertical>; -using altitude = vector, direction::vertical>; +struct horizontal_vector : kind {}; +struct vertical_vector : kind {}; +struct vertical_point : point_kind {}; +struct velocity_vector : derived_kind {}; +struct rate_of_climb_vector : derived_kind {}; + +using distance = quantity_kind; +using height = quantity_kind; +using altitude = quantity_point_kind; using duration = si::time; -using velocity = vector, direction::horizontal>; -using rate_of_climb = vector, direction::vertical>; +using velocity = quantity_kind; +using rate_of_climb = quantity_kind; template std::basic_ostream& operator<<(std::basic_ostream& os, const altitude& a) { - return os << a.magnitude().relative() << " AMSL"; + return os << a.relative().common() << " AMSL"; } } // namespace @@ -178,7 +72,7 @@ struct fmt::formatter : formatter> { template auto format(altitude a, FormatContext& ctx) { - formatter>::format(a.magnitude().relative(), ctx); + formatter>::format(a.relative().common(), ctx); return format_to(ctx.out(), " AMSL"); } }; @@ -201,8 +95,15 @@ struct fmt::formatter : formatter> { // gliders database namespace { -using namespace units::physical::si::literals; -using namespace units::physical::si::international::literals; +using namespace si::literals; +using namespace si::international::literals; +using namespace si::unit_constants; + +template +constexpr Quantity auto operator/(const QK1& lhs, const QK2& rhs) + requires requires { lhs.common() / rhs.common(); } { + return lhs.common() / rhs.common(); +} struct glider { struct polar_point { @@ -217,10 +118,10 @@ struct glider { auto get_gliders() { const std::array gliders = { - glider{"SZD-30 Pirat", {velocity(83_q_km_per_h), rate_of_climb(-0.7389_q_m_per_s)}}, - glider{"SZD-51 Junior", {velocity(80_q_km_per_h), rate_of_climb(-0.6349_q_m_per_s)}}, - glider{"SZD-48 Jantar Std 3", {velocity(110_q_km_per_h), rate_of_climb(-0.77355_q_m_per_s)}}, - glider{"SZD-56 Diana", {velocity(110_q_km_per_h), rate_of_climb(-0.63657_q_m_per_s)}} + glider{"SZD-30 Pirat", {velocity(83 * km / h), rate_of_climb(-0.7389 * m / s)}}, + glider{"SZD-51 Junior", {velocity(80 * km / h), rate_of_climb(-0.6349 * m / s)}}, + glider{"SZD-48 Jantar Std 3", {velocity(110 * km / h), rate_of_climb(-0.77355 * m / s)}}, + glider{"SZD-56 Diana", {velocity(110 * km / h), rate_of_climb(-0.63657 * m / s)}} }; return gliders; } @@ -235,7 +136,7 @@ template void print(const R& gliders) { std::cout << "Gliders:\n"; - std::cout << "========\n"; + std::cout << "========\n"; for(const auto& g : gliders) { std::cout << "- Name: " << g.name << "\n"; std::cout << "- Polar:\n"; @@ -258,9 +159,9 @@ struct weather { auto get_weather_conditions() { const std::array weather_conditions = { - std::pair("Good", weather{height(1900_q_m), rate_of_climb(4.3_q_m_per_s)}), - std::pair("Medium", weather{height(1550_q_m), rate_of_climb(2.8_q_m_per_s)}), - std::pair("Bad", weather{height(850_q_m), rate_of_climb(1.8_q_m_per_s)}) + std::pair("Good", weather{height(1900 * m), rate_of_climb(4.3 * m / s)}), + std::pair("Medium", weather{height(1550 * m), rate_of_climb(2.8 * m / s)}), + std::pair("Bad", weather{height(850 * m), rate_of_climb(1.8 * m / s)}) }; return weather_conditions; } @@ -270,7 +171,7 @@ template void print(const R& conditions) { std::cout << "Weather:\n"; - std::cout << "========\n"; + std::cout << "========\n"; for(const auto& c : conditions) { std::cout << "- Kind: " << c.first << "\n"; const auto& w = c.second; @@ -409,12 +310,12 @@ flight_point circle(const flight_point& point, const glider& g, const weather& w constexpr distance glide_distance(const flight_point& point, const glider& g, const task& t, const safety& s, altitude ground_alt) { const auto dist_to_finish = t.dist - point.dist; - return distance((ground_alt + s.min_agl_height - point.alt).magnitude() / ((ground_alt - t.finish.alt) / dist_to_finish - 1 / glide_ratio(g.polar[0]))); + return distance((ground_alt + s.min_agl_height - point.alt).common() / ((ground_alt - t.finish.alt) / dist_to_finish - 1 / glide_ratio(g.polar[0]))); } inline si::length length_3d(distance dist, height h) { - return sqrt(pow<2>(dist.magnitude()) + pow<2>(h.magnitude())); + return sqrt(pow<2>(dist.common()) + pow<2>(h.common())); } flight_point glide(const flight_point& point, const glider& g, const task& t, const safety& s) @@ -423,7 +324,7 @@ flight_point glide(const flight_point& point, const glider& g, const task& t, co const auto dist = glide_distance(point, g, t, s, ground_alt); const auto alt = ground_alt + s.min_agl_height; const auto l3d = length_3d(dist, point.alt - alt); - const duration d = l3d / g.polar[0].v.magnitude(); + const duration d = l3d / g.polar[0].v.common(); const flight_point new_point{point.dur + d, point.dist + dist, terrain_level_alt(t, point.dist + dist) + s.min_agl_height}; print("Glide", point, new_point); @@ -434,7 +335,7 @@ flight_point final_glide(const flight_point& point, const glider& g, const task& { const auto dist = t.dist - point.dist; const auto l3d = length_3d(dist, point.alt - t.finish.alt); - const duration d = l3d / g.polar[0].v.magnitude(); + const duration d = l3d / g.polar[0].v.common(); const flight_point new_point{point.dur + d, point.dist + dist, t.finish.alt}; print("Final Glide", point, new_point); @@ -450,7 +351,7 @@ void estimate(const glider& g, const weather& w, const task& t, const safety& s, point = tow(point, at); // estimate the altitude needed to reach the finish line from this place - const altitude final_glide_alt = t.finish.alt + height(t.dist.magnitude() / glide_ratio(g.polar[0])); + const altitude final_glide_alt = t.finish.alt + height(t.dist.common() / glide_ratio(g.polar[0])); // how much height we still need to gain in the thermalls to reach the destination? height height_to_gain = final_glide_alt - point.alt; @@ -462,7 +363,7 @@ void estimate(const glider& g, const weather& w, const task& t, const safety& s, // circle in a thermall to gain height point = circle(point, g, w, t, height_to_gain); } - while(height_to_gain > height(0_q_m)); + while(height_to_gain > height(0 * m)); // final glide point = final_glide(point, g, t); @@ -488,10 +389,10 @@ void example() }; print(t); - const safety s = {height(300_q_m)}; + const safety s = {height(300 * m)}; print(s); - const aircraft_tow tow = {height(400_q_m), rate_of_climb(1.6_q_m_per_s)}; + const aircraft_tow tow = {height(400 * m), rate_of_climb(1.6 * m / ::s)}; print(tow); for(const auto& g : gliders) { diff --git a/src/include/units/bits/basic_concepts.h b/src/include/units/bits/basic_concepts.h index cb068f29..43602c86 100644 --- a/src/include/units/bits/basic_concepts.h +++ b/src/include/units/bits/basic_concepts.h @@ -202,7 +202,51 @@ concept UnitOf = Dimension && std::same_as::reference>; -// Quantity, QuantityPoint +// Kind +namespace detail { + +template +struct _kind_base; + +} // namespace detail + +template typename Base> +concept kind_impl_ = + is_derived_from_specialization_of && + requires(T* t) { + typename T::base_kind; + typename T::dimension; + requires Dimension; + }; + +/** + * @brief A concept matching all kind types + * + * Satisfied by all kind types derived from an specialization of :class:`kind`. + */ +template +concept Kind = + kind_impl_ && + kind_impl_ && + std::same_as; + +// PointKind +namespace detail { + +template +struct _point_kind_base; + +} // namespace detail + +/** + * @brief A concept matching all point kind types + * + * Satisfied by all point kind types derived from an specialization of :class:`point_kind`. + */ +template +concept PointKind = kind_impl_; + +// Quantity, QuantityPoint, QuantityKind, QuantityPointKind namespace detail { template @@ -211,6 +255,12 @@ inline constexpr bool is_quantity = false; template inline constexpr bool is_quantity_point = false; +template +inline constexpr bool is_quantity_kind = false; + +template +inline constexpr bool is_quantity_point_kind = false; + } // namespace detail /** @@ -229,9 +279,27 @@ concept Quantity = detail::is_quantity; template concept QuantityPoint = detail::is_quantity_point; +/** + * @brief A concept matching all quantity kinds in the library. + * + * Satisfied by all specializations of @c quantity_kind. + */ +template +concept QuantityKind = detail::is_quantity_kind; + +/** + * @brief A concept matching all quantity point kinds in the library. + * + * Satisfied by all specializations of @c quantity_point_kind. + */ +template +concept QuantityPointKind = detail::is_quantity_point_kind; + +// QuantityLike + /** * @brief A concept matching all quantity-like types (other than specialization of @c quantity) - * + * * Satisfied by all types for which a correct specialization of `quantity_like_traits` * type trait is provided. */ @@ -261,12 +329,12 @@ concept castable_number_ = // exposition only common_type_with_ && scalable_number_>; -template +template concept scalable_ = // exposition only castable_number_ || (requires { typename T::value_type; } && castable_number_ && scalable_number_>); -template +template concept scalable_with_ = // exposition only common_type_with_ && scalable_>; @@ -281,6 +349,10 @@ template requires requires { typename T::value_type; } inline constexpr bool is_wrapped_quantity = Quantity || QuantityLike || is_wrapped_quantity; +template + requires requires { typename T::quantity_type; } +inline constexpr bool is_wrapped_quantity = Quantity; + } // namespace detail /** diff --git a/src/include/units/bits/common_quantity.h b/src/include/units/bits/common_quantity.h index 00c4b2b3..a7970816 100644 --- a/src/include/units/bits/common_quantity.h +++ b/src/include/units/bits/common_quantity.h @@ -34,6 +34,12 @@ class quantity; template U, QuantityValue Rep> class quantity_point; +template U, QuantityValue Rep> +class quantity_kind; + +template U, QuantityValue Rep> +class quantity_point_kind; + namespace detail { template @@ -65,33 +71,53 @@ struct common_quantity_impl, quantity, Rep> using type = quantity; }; -template -quantity_point common_quantity_point_impl(quantity); - } // namespace detail template Q2, QuantityValue Rep = std::common_type_t> using common_quantity = TYPENAME detail::common_quantity_impl::type; -template - requires requires { typename common_quantity; } -using common_quantity_point = decltype( - detail::common_quantity_point_impl(common_quantity{})); +template QP2> +using common_quantity_point = std::common_type_t; + +template QK2> +using common_quantity_kind = std::common_type_t; + +template QPK2> +using common_quantity_point_kind = std::common_type_t; } // namespace units namespace std { -template - requires units::equivalent +template Q2> + requires requires { typename common_type_t; } struct common_type { using type = units::common_quantity; }; -template - requires requires { typename units::common_quantity_point; } +template QP2> + requires requires { typename common_type_t; } struct common_type { - using type = units::common_quantity_point; + using type = units::quantity_point< + typename common_type_t::dimension, + typename common_type_t::unit, + typename common_type_t::rep>; +}; + +template QK2> + requires requires { typename common_type_t; } +struct common_type { + using type = units::quantity_kind::unit, + typename common_type_t::rep>; +}; + +template QPK2> + requires requires { typename common_type_t; } +struct common_type { + using type = units::quantity_point_kind::unit, + typename common_type_t::rep>; }; } diff --git a/src/include/units/bits/equivalent.h b/src/include/units/bits/equivalent.h index 898e6426..80825870 100644 --- a/src/include/units/bits/equivalent.h +++ b/src/include/units/bits/equivalent.h @@ -41,7 +41,7 @@ struct equivalent_impl : std::true_type { // units template -struct equivalent_impl : std::is_base_of, std::is_base_of {}; +struct equivalent_impl : std::disjunction, std::is_base_of> {}; // dimensions @@ -81,7 +81,17 @@ template struct equivalent_unit : std::disjunction, std::bool_constant::ratio == U2::ratio / dimension_unit::ratio>> {}; -// quantities and quantity points + +// (point) kinds + +template + requires (Kind && Kind) || (PointKind && PointKind) +struct equivalent_impl : + std::conjunction, + equivalent_impl> {}; + + +// quantities, quantity points, quantity (point) kinds template requires (Quantity && Quantity) || (QuantityPoint && QuantityPoint) @@ -89,6 +99,11 @@ struct equivalent_impl : std::conjunction> {}; +template + requires (QuantityKind && QuantityKind) || (QuantityPointKind && QuantityPointKind) +struct equivalent_impl : std::conjunction, + equivalent_impl> {}; + } // namespace detail template diff --git a/src/include/units/bits/quantity_of.h b/src/include/units/bits/quantity_of.h index 27841311..cd319cab 100644 --- a/src/include/units/bits/quantity_of.h +++ b/src/include/units/bits/quantity_of.h @@ -79,4 +79,41 @@ concept QuantityOf = Quantity && Dimension && equivalent concept QuantityEquivalentTo = Quantity && QuantityOf; +/** + * @brief A concept matching all quantity points with provided dimension + * + * Satisfied by all quantity points with a dimension being the instantiation derived from + * the provided dimension type. + */ +template +concept QuantityPointOf = QuantityPoint && Dimension && equivalent; + +template +concept QuantityPointEquivalentTo = QuantityPoint && QuantityPointOf; + +/** + * @brief A concept matching only quantity kinds of a specific kind. + * + * @tparam QK Quantity kind to verify. + * @tparam K Kind type to use for verification. + */ +template +concept QuantityKindOf = QuantityKind && Kind && equivalent; + +template +concept QuantityKindEquivalentTo = QuantityKind && QuantityKindOf; + +/** + * @brief A concept matching only quantity point kinds of a specific point kind. + * + * @tparam QPK Quantity point kind to verify. + * @tparam PK Point kind type to use for verification. + */ +template +concept QuantityPointKindOf = QuantityPointKind && PointKind && equivalent; + +template +concept QuantityPointKindEquivalentTo = + QuantityPointKind && QuantityPointKindOf; + } // namespace units diff --git a/src/include/units/kind.h b/src/include/units/kind.h new file mode 100644 index 00000000..7b1eef7f --- /dev/null +++ b/src/include/units/kind.h @@ -0,0 +1,64 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include +#include + +namespace units { + +namespace detail { + +template +struct _kind_base : downcast_base<_kind_base> { + using base_kind = K; + using dimension = D; +}; + +template +struct _point_kind_base : downcast_base<_point_kind_base> { + using base_kind = K; + using dimension = typename K::dimension; +}; + +} // namespace detail + +template + requires Kind>> +using downcast_kind = downcast>; + +template + requires PointKind>> +using downcast_point_kind = downcast>; + +template +struct kind : downcast_dispatch> {}; + +template + requires std::same_as +struct derived_kind : downcast_dispatch> {}; + +template +struct point_kind : downcast_dispatch> {}; + +} // namespace units diff --git a/src/include/units/quantity_cast.h b/src/include/units/quantity_cast.h index 582cfb38..accb2c0a 100644 --- a/src/include/units/quantity_cast.h +++ b/src/include/units/quantity_cast.h @@ -42,6 +42,12 @@ class quantity; template U, QuantityValue Rep> class quantity_point; +template U, QuantityValue Rep> +class quantity_kind; + +template U, QuantityValue Rep> +class quantity_point_kind; + namespace detail { template @@ -80,7 +86,7 @@ struct cast_traits { template requires (!common_type_with_, std::intmax_t>) && - scalable_number_, std::intmax_t> && + scalable_number_, std::intmax_t> && requires { typename std::common_type_t::value_type; } && common_type_with_::value_type, std::intmax_t> struct cast_traits { @@ -103,7 +109,7 @@ struct cast_traits { * @tparam To a target quantity type to cast to */ template Rep> - requires QuantityOf + requires QuantityOf && std::constructible_from> [[nodiscard]] constexpr auto quantity_cast(const quantity& q) { using traits = detail::cast_traits; @@ -137,7 +143,7 @@ template R * * This cast gets only the target dimension to cast to. For example: * - * auto q1 = units::quantity_cast(200_q_Gal); + * auto q1 = units::quantity_cast(200_q_Gal); * * @tparam ToD a dimension type to use for a target quantity */ @@ -179,7 +185,7 @@ template * * @note This cast is especially useful when working with quantities of unknown dimensions * (@c unknown_dimension). - * + * * @tparam ToD a dimension type to use for a target quantity * @tparam ToU a unit type to use for a target quantity */ @@ -203,6 +209,7 @@ template * @tparam ToRep a representation type to use for a target quantity */ template Rep> + requires std::constructible_from> [[nodiscard]] constexpr auto quantity_cast(const quantity& q) { return quantity_cast>(q); @@ -218,7 +225,7 @@ template Rep> * * auto q1 = units::quantity_point_cast(quantity_point{1_q_ms}); * auto q1 = units::quantity_point_cast>(quantity_point{1_q_ms}); - * auto q1 = units::quantity_point_cast(quantity_point{200_q_Gal}); + * auto q1 = units::quantity_point_cast(quantity_point{200_q_Gal}); * auto q1 = units::quantity_point_cast(quantity_point{1_q_ms}); * auto q1 = units::quantity_point_cast(quantity_point{1_q_ms}); * @@ -247,7 +254,7 @@ template * * @note This cast is especially useful when working with quantity points of unknown dimensions * (@c unknown_dimension). - * + * * @tparam ToD a dimension type to use for a target quantity * @tparam ToU a unit type to use for a target quantity */ @@ -258,6 +265,119 @@ template return quantity_point_cast>(q); } +/** + * @brief Explicit cast of a quantity kind + * + * Implicit conversions between quantity kinds of different types are allowed only for "safe" + * (i.e. non-truncating) conversion. In other cases an explicit cast has to be used. + * + * This cast gets the target (quantity) kind type to cast to or anything that works for quantity_cast. For example: + * + * auto q1 = units::quantity_kind_cast(quantity_kind{ns::width{1 * mm}); + * auto q1 = units::quantity_kind_cast(ns::width{1 * m}); + * auto q1 = units::quantity_kind_cast>(ns::width{1 * mm}); + * auto q1 = units::quantity_kind_cast(ns::rate_of_climb{200 * Gal}); + * auto q1 = units::quantity_kind_cast(ns::width{1 * mm}); + * auto q1 = units::quantity_kind_cast(ns::width{1.0 * mm}); + * + * @tparam CastSpec a target (quantity) kind type to cast to or anything that works for quantity_cast + */ +template +[[nodiscard]] constexpr QuantityKind auto quantity_kind_cast(const quantity_kind& qk) + requires (is_specialization_of && + requires { quantity_cast(qk.common()); }) || + (Kind && UnitOf) || + requires { quantity_cast(qk.common()); } +{ + if constexpr (is_specialization_of) + return CastSpec(quantity_cast(qk.common())); + else if constexpr (Kind) + return quantity_kind(qk.common()); + else { + auto q{quantity_cast(qk.common())}; + using Q = decltype(q); + return quantity_kind(static_cast(q)); + } +} + +/** + * @brief Explicit cast of a quantity kind + * + * Implicit conversions between quantity kinds of different types are allowed only for "safe" + * (i.e. non-truncating) conversion. In other cases an explicit cast has to be used. + * + * This cast gets both the target kind and unit to cast to. For example: + * + * auto q1 = units::quantity_kind_cast(w); + * + * @note This cast is especially useful when working with quantity kinds of unknown kind. + * + * @tparam ToK the kind type to use for the target quantity + * @tparam ToU the unit type to use for the target quantity + */ +template + requires equivalent && UnitOf +[[nodiscard]] constexpr QuantityKind auto quantity_kind_cast(const quantity_kind& qk) +{ + return quantity_kind_cast>(qk); +} + +/** + * @brief Explicit cast of a quantity point kind + * + * Implicit conversions between quantity point kinds of different types are allowed only for "safe" + * (i.e. non-truncating) conversion. In other cases an explicit cast has to be used. + * + * This cast gets the target (quantity) point kind type to cast to or anything that works for quantity_kind_cast. For example: + * + * auto q1 = units::quantity_point_kind_cast(ns::x_coordinate{1 * mm}); + * auto q1 = units::quantity_point_kind_cast(ns::x_coordinate{1 * mm}); + * auto q1 = units::quantity_point_kind_cast(ns::x_coordinate{1 * m}); + * auto q1 = units::quantity_point_kind_cast(ns::x_coordinate{1 * m}); + * auto q1 = units::quantity_point_kind_cast>(ns::x_coordinate{1 * mm}); + * auto q1 = units::quantity_point_kind_cast(quantity_point_kind(ns::rate_of_climb{200 * Gal})); + * auto q1 = units::quantity_point_kind_cast(ns::x_coordinate{1 * mm}); + * auto q1 = units::quantity_point_kind_cast(ns::x_coordinate{1.0 * mm}); + * + * @tparam CastSpec a target (quantity) point kind type to cast to or anything that works for quantity_kind_cast + */ +template +[[nodiscard]] constexpr QuantityPointKind auto quantity_point_kind_cast(const quantity_point_kind& qpk) + requires (is_specialization_of && + requires { quantity_kind_cast(qpk.relative()); }) || + (PointKind && UnitOf) || + requires { quantity_kind_cast(qpk.relative()); } +{ + if constexpr (is_specialization_of) + return CastSpec(quantity_kind_cast(qpk.relative())); + else if constexpr (PointKind) + return quantity_point_kind(quantity_kind_cast(qpk.relative())); + else + return quantity_point_kind(quantity_kind_cast(qpk.relative())); +} + +/** + * @brief Explicit cast of a quantity point kind + * + * Implicit conversions between quantity point kinds of different types are allowed only for "safe" + * (i.e. non-truncating) conversion. In other cases an explicit cast has to be used. + * + * This cast gets both the target point kind and unit to cast to. For example: + * + * auto q1 = units::quantity_point_kind_cast(x); + * + * @note This cast is especially useful when working with quantity point kinds of unknown point kind. + * + * @tparam ToPK the point kind type to use for the target quantity + * @tparam ToU the unit type to use for the target quantity + */ +template + requires equivalent && UnitOf +[[nodiscard]] constexpr QuantityPointKind auto quantity_point_kind_cast(const quantity_point_kind& qpk) +{ + return quantity_point_kind_cast>(qpk); +} + } // namespace units #ifdef _MSC_VER diff --git a/src/include/units/quantity_kind.h b/src/include/units/quantity_kind.h new file mode 100644 index 00000000..6dc5ed66 --- /dev/null +++ b/src/include/units/quantity_kind.h @@ -0,0 +1,351 @@ + +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include +#include +#include +#include +#include + +namespace units { + +namespace detail { + +template +inline constexpr auto make_quantity_kind_fn = [](auto q) { + using Q = decltype(q); + return quantity_kind(std::move(q)); +}; + +template +inline constexpr auto& make_quantity_kind = make_quantity_kind_fn; + +template +inline constexpr auto downcasted_kind_fn = [](auto q) { + using Q = decltype(q); + return make_quantity_kind_fn>(std::move(q)); +}; + +template +inline constexpr auto& downcasted_kind = downcasted_kind_fn; + +} // namespace detail + +/** + * @brief A quantity kind + * + * A quantity with more specific usage as determined by its kind. + * See https://jcgm.bipm.org/vim/en/1.2.html and NOTE 1 at https://jcgm.bipm.org/vim/en/1.1.html. + * + * @tparam K the kind of quantity + * @tparam U the measurement unit of the quantity kind + * @tparam Rep the type to be used to represent values of the quantity kind + */ +template U, QuantityValue Rep = double> +class quantity_kind { +public: + using kind_type = K; + using quantity_type = quantity; + using dimension = typename quantity_type::dimension; + using unit = typename quantity_type::unit; + using rep = typename quantity_type::rep; + +private: + quantity_type q_; + +public: + quantity_kind() = default; + quantity_kind(const quantity_kind&) = default; + quantity_kind(quantity_kind&&) = default; + + template Value> + requires is_same_v && std::is_constructible_v + constexpr explicit quantity_kind(const Value& v) : q_(v) {} + + template + requires (Quantity || QuantityLike) && std::is_constructible_v + constexpr explicit quantity_kind(const Q& q) : q_{q} {} + + template QK2> + requires std::is_convertible_v + constexpr explicit(false) quantity_kind(const QK2& qk) : q_{qk.common()} {} + + quantity_kind& operator=(const quantity_kind&) = default; + quantity_kind& operator=(quantity_kind&&) = default; + + [[nodiscard]] constexpr quantity_type common() const noexcept { return q_; } + + [[nodiscard]] static constexpr quantity_kind zero() noexcept + requires requires { quantity_type::zero(); } + { + return quantity_kind(quantity_type::zero()); + } + + [[nodiscard]] static constexpr quantity_kind one() noexcept + requires requires { quantity_type::one(); } + { + return quantity_kind(quantity_type::one()); + } + + [[nodiscard]] static constexpr quantity_kind min() noexcept + requires requires { quantity_type::min(); } + { + return quantity_kind(quantity_type::min()); + } + + [[nodiscard]] static constexpr quantity_kind max() noexcept + requires requires { quantity_type::max(); } + { + return quantity_kind(quantity_type::max()); + } + + [[nodiscard]] constexpr quantity_kind operator+() const + requires requires(quantity_type q) { +q; } + { + return *this; + } + + [[nodiscard]] constexpr QuantityKind auto operator-() const + requires requires(quantity_type q) { -q; } + { + return detail::make_quantity_kind(-q_); + } + + constexpr quantity_kind& operator++() + requires requires(quantity_type q) { ++q; } + { + ++q_; + return *this; + } + + [[nodiscard]] constexpr quantity_kind operator++(int) + requires requires(quantity_type q) { q++; } + { + return quantity_kind(q_++); + } + + constexpr quantity_kind& operator--() + requires requires(quantity_type q) { --q; } + { + --q_; + return *this; + } + + [[nodiscard]] constexpr quantity_kind operator--(int) + requires requires(quantity_type q) { q--; } + { + return quantity_kind(q_--); + } + + constexpr quantity_kind& operator+=(const quantity_kind& qk) + requires requires(quantity_type q) { q += qk.common(); } + { + q_ += qk.common(); + return *this; + } + + constexpr quantity_kind& operator-=(const quantity_kind& qk) + requires requires(quantity_type q) { q -= qk.common(); } + { + q_ -= qk.common(); + return *this; + } + + template + constexpr quantity_kind& operator*=(const Rep2& rhs) + requires requires(quantity_type q) { q *= rhs; } + { + q_ *= rhs; + return *this; + } + + template + constexpr quantity_kind& operator/=(const Rep2& rhs) + requires requires(quantity_type q) { q /= rhs; } + { + q_ /= rhs; + return *this; + } + + constexpr quantity_kind& operator%=(const rep& rhs) + requires requires(quantity_type q) { q %= rhs; } + { + q_ %= rhs; + return *this; + } + + constexpr quantity_kind& operator%=(const quantity_kind& qk) + requires requires(quantity_type q) { q %= qk.common(); } + { + q_ %= qk.common(); + return *this; + } + + // Hidden Friends + // Below friend functions are to be found via argument-dependent lookup only + [[nodiscard]] friend constexpr quantity_kind operator+(const quantity_kind& lhs, const quantity_kind& rhs) + requires requires { lhs.common() + rhs.common(); } + { + return quantity_kind(lhs.common() + rhs.common()); + } + + [[nodiscard]] friend constexpr quantity_kind operator-(const quantity_kind& lhs, const quantity_kind& rhs) + requires requires { lhs.common() - rhs.common(); } + { + return quantity_kind(lhs.common() - rhs.common()); + } + + template + [[nodiscard]] friend constexpr QuantityKind auto operator*(const quantity_kind& qk, const Value& v) + requires requires { { qk.common() * v } -> Quantity; } + { + return detail::make_quantity_kind(qk.common() * v); + } + + template + [[nodiscard]] friend constexpr QuantityKind auto operator*(const Value& v, const quantity_kind& qk) + requires requires { { v * qk.common() } -> Quantity; } + { + return detail::make_quantity_kind(v * qk.common()); + } + + template + [[nodiscard]] friend constexpr QuantityKind auto operator/(const quantity_kind& qk, const Value& v) + requires requires { { qk.common() / v } -> Quantity; } + { + return detail::make_quantity_kind(qk.common() / v); + } + + template + [[nodiscard]] friend constexpr QuantityKind auto operator/(const Value& v, const quantity_kind& qk) + requires requires { { v / qk.common() } -> Quantity; } + { + return detail::downcasted_kind(v / qk.common()); + } + + template + [[nodiscard]] friend constexpr QuantityKind auto operator%(const quantity_kind& qk, const Value& v) + requires requires { qk.common() % v; } + { + return detail::make_quantity_kind(qk.common() % v); + } + + [[nodiscard]] friend constexpr quantity_kind operator%(const quantity_kind& lhs, const quantity_kind& rhs) + requires requires { lhs.common() % rhs.common(); } + { + return quantity_kind(lhs.common() % rhs.common()); + } + + [[nodiscard]] friend constexpr auto operator<=>(const quantity_kind& lhs, const quantity_kind& rhs) + requires std::three_way_comparable + { + return lhs.common() <=> rhs.common(); + } + + [[nodiscard]] friend constexpr bool operator==(const quantity_kind&, const quantity_kind&) = default; +}; + +template +explicit(false) quantity_kind(K, V) -> quantity_kind; + +template QK2> +[[nodiscard]] constexpr QuantityKind auto operator+(const QK1& lhs, const QK2& rhs) + requires requires { lhs.common() + rhs.common(); } +{ + return detail::make_quantity_kind(lhs.common() + rhs.common()); +} + +template QK2> +[[nodiscard]] constexpr QuantityKind auto operator-(const QK1& lhs, const QK2& rhs) + requires requires { lhs.common() - rhs.common(); } +{ + return detail::make_quantity_kind(lhs.common() - rhs.common()); +} + +template +[[nodiscard]] constexpr QuantityKind auto operator*(const QK& lhs, const Q& rhs) + requires requires { lhs.common() * rhs; } +{ + return detail::downcasted_kind(lhs.common() * rhs); +} + +template +[[nodiscard]] constexpr QuantityKind auto operator*(const Q& lhs, const QK& rhs) + requires requires { lhs * rhs.common(); } +{ + return detail::downcasted_kind(lhs * rhs.common()); +} + +template +[[nodiscard]] constexpr QuantityKind auto operator/(const QK& lhs, const Q& rhs) + requires requires { lhs.common() / rhs; } +{ + return detail::downcasted_kind(lhs.common() / rhs); +} + +template +[[nodiscard]] constexpr QuantityKind auto operator/(const Q& lhs, const QK& rhs) + requires requires { lhs / rhs.common(); } +{ + return detail::downcasted_kind(lhs / rhs.common()); +} + +template +[[nodiscard]] constexpr QuantityKind auto operator%(const QK& lhs, const D& rhs) + requires requires { lhs.common() % rhs; } +{ + return detail::make_quantity_kind(lhs.common() % rhs); +} + +template QK2> +[[nodiscard]] constexpr QuantityKind auto operator%(const QK1& lhs, const QK2& rhs) + requires requires { lhs.common() % rhs.common(); } +{ + return detail::make_quantity_kind(lhs.common() % rhs.common()); +} + +template QK2> + requires std::three_way_comparable_with +[[nodiscard]] constexpr auto operator<=>(const QK1& lhs, const QK2& rhs) +{ + return lhs.common() <=> rhs.common(); +} + +template QK2> + requires std::equality_comparable_with +[[nodiscard]] constexpr bool operator==(const QK1& lhs, const QK2& rhs) +{ + return lhs.common() == rhs.common(); +} + +// type traits +namespace detail { + +template +inline constexpr bool is_quantity_kind> = true; + +} // namespace detail + +} // namespace units diff --git a/src/include/units/quantity_point.h b/src/include/units/quantity_point.h index cecdc716..a2506956 100644 --- a/src/include/units/quantity_point.h +++ b/src/include/units/quantity_point.h @@ -53,8 +53,14 @@ public: quantity_point(const quantity_point&) = default; quantity_point(quantity_point&&) = default; - template - requires std::is_convertible_v + template Value> + requires is_same_v && std::is_constructible_v + constexpr explicit quantity_point(const Value& v) : q_(v) {} + + constexpr explicit quantity_point(const quantity_type& q) : q_{q} {} + + template + requires std::is_constructible_v constexpr explicit quantity_point(const Q& q) : q_{q} {} template @@ -169,8 +175,12 @@ public: }; -template -quantity_point(quantity) -> quantity_point; +template +explicit(false) quantity_point(V) -> quantity_point; + +template +quantity_point(Q) -> quantity_point::dimension, + typename quantity_like_traits::unit, typename quantity_like_traits::rep>; namespace detail { diff --git a/src/include/units/quantity_point_kind.h b/src/include/units/quantity_point_kind.h new file mode 100644 index 00000000..83cd8f4c --- /dev/null +++ b/src/include/units/quantity_point_kind.h @@ -0,0 +1,195 @@ + +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#pragma once + +#include +#include +#include + +namespace units { + +/** + * @brief A quantity point kind + * + * An absolute quantity kind with respect to zero (which represents some origin). + * + * @tparam PK the point kind of quantity point + * @tparam U the measurement unit of the quantity point kind + * @tparam Rep the type to be used to represent values of the quantity point kind + */ +template U, QuantityValue Rep = double> +class quantity_point_kind { +public: + using point_kind_type = PK; + using kind_type = typename PK::base_kind; + using quantity_kind_type = quantity_kind; + using quantity_type = typename quantity_kind_type::quantity_type; + using dimension = typename quantity_type::dimension; + using unit = typename quantity_type::unit; + using rep = typename quantity_type::rep; + +private: + quantity_kind_type qk_; + +public: + quantity_point_kind() = default; + quantity_point_kind(const quantity_point_kind&) = default; + quantity_point_kind(quantity_point_kind&&) = default; + + template Value> + requires std::is_constructible_v + constexpr explicit quantity_point_kind(const Value& v) : qk_(v) {} + + constexpr explicit quantity_point_kind(const quantity_type& q) : qk_{q} {} + + template + requires std::is_constructible_v + constexpr explicit quantity_point_kind(const Q& q) : qk_{q} {} + + constexpr explicit quantity_point_kind(const quantity_point& qp) : qk_{qp.relative()} {} + + constexpr explicit quantity_point_kind(const quantity_kind_type& qk) : qk_{qk} {} + + template QPK2> + requires std::is_convertible_v + constexpr explicit(false) quantity_point_kind(const QPK2& qpk) : qk_{qpk.relative()} {} + + quantity_point_kind& operator=(const quantity_point_kind&) = default; + quantity_point_kind& operator=(quantity_point_kind&&) = default; + + [[nodiscard]] constexpr quantity_kind_type relative() const noexcept { return qk_; } + + [[nodiscard]] static constexpr quantity_point_kind min() noexcept + requires requires { quantity_kind_type::min(); } + { + return quantity_point_kind(quantity_kind_type::min()); + } + + [[nodiscard]] static constexpr quantity_point_kind max() noexcept + requires requires { quantity_kind_type::max(); } + { + return quantity_point_kind(quantity_kind_type::max()); + } + + constexpr quantity_point_kind& operator++() + requires requires(quantity_kind_type qk) { ++qk; } + { + ++qk_; + return *this; + } + + [[nodiscard]] constexpr quantity_point_kind operator++(int) + requires requires(quantity_kind_type qk) { qk++; } + { + return quantity_point_kind(qk_++); + } + + constexpr quantity_point_kind& operator--() + requires requires(quantity_kind_type qk) { --qk; } + { + --qk_; + return *this; + } + + [[nodiscard]] constexpr quantity_point_kind operator--(int) + requires requires(quantity_kind_type qk) { qk--; } + { + return quantity_point_kind(qk_--); + } + + constexpr quantity_point_kind& operator+=(const quantity_kind_type& qk) + requires requires(quantity_kind_type qk1, const quantity_kind_type qk2) { qk1 += qk2; } + { + qk_ += qk; + return *this; + } + + constexpr quantity_point_kind& operator-=(const quantity_kind_type& qk) + requires requires(quantity_kind_type qk1, const quantity_kind_type qk2) { qk1 -= qk2; } + { + qk_ -= qk; + return *this; + } + + // Hidden Friends + // Below friend functions are to be found via argument-dependent lookup only + + template + [[nodiscard]] friend constexpr QuantityPointKind auto operator+(const quantity_point_kind& lhs, const QK& rhs) + requires requires { lhs.relative() + rhs; } + { + return units::quantity_point_kind(lhs.relative() + rhs); + } + + template + [[nodiscard]] friend constexpr QuantityPointKind auto operator+(const QK& lhs, const quantity_point_kind& rhs) + requires requires { lhs + rhs.relative(); } + { + return units::quantity_point_kind(lhs + rhs.relative()); + } + + template + [[nodiscard]] friend constexpr QuantityPointKind auto operator-(const quantity_point_kind& lhs, const QK& rhs) + requires requires { lhs.relative() - rhs; } + { + return units::quantity_point_kind(lhs.relative() - rhs); + } + + [[nodiscard]] friend constexpr QuantityKind auto operator-(const quantity_point_kind& lhs, const quantity_point_kind& rhs) + requires requires { lhs.relative() - rhs.relative(); } + { + return lhs.relative() - rhs.relative(); + } + + template + requires std::three_way_comparable_with + [[nodiscard]] friend constexpr auto operator<=>(const quantity_point_kind& lhs, const QPK& rhs) + { + return lhs.relative() <=> rhs.relative(); + } + + template + requires std::equality_comparable_with + [[nodiscard]] friend constexpr bool operator==(const quantity_point_kind& lhs, const QPK& rhs) + { + return lhs.relative() == rhs.relative(); + } + +}; + +template +explicit(false) quantity_point_kind(PK, V) -> quantity_point_kind; + +template +quantity_point_kind(QK) -> + quantity_point_kind, typename QK::unit, typename QK::rep>; + +namespace detail { + +template +inline constexpr bool is_quantity_point_kind> = true; + +} // namespace detail + +} // namespace units diff --git a/test/unit_test/static/CMakeLists.txt b/test/unit_test/static/CMakeLists.txt index a98c54ca..e0258c39 100644 --- a/test/unit_test/static/CMakeLists.txt +++ b/test/unit_test/static/CMakeLists.txt @@ -34,9 +34,12 @@ add_library(unit_tests_static dimensions_concepts_test.cpp fixed_string_test.cpp fps_test.cpp + kind_test.cpp math_test.cpp - quantity_point_test.cpp quantity_test.cpp + quantity_point_test.cpp + quantity_kind_test.cpp + quantity_point_kind_test.cpp ratio_test.cpp si_test.cpp si_cgs_test.cpp diff --git a/test/unit_test/static/kind_test.cpp b/test/unit_test/static/kind_test.cpp new file mode 100644 index 00000000..9915b011 --- /dev/null +++ b/test/unit_test/static/kind_test.cpp @@ -0,0 +1,203 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "test_tools.h" +#include "units/generic/angle.h" +#include "units/kind.h" +#include "units/physical/si/base/length.h" +#include "units/physical/si/derived/area.h" +#include "units/physical/si/derived/speed.h" + +using namespace units; +using namespace physical::si; + +namespace { + +template +using downcast_result = std::conditional_t>; + + +// no library-defined base kind + + +// spherical coordinates +struct radius : kind {}; // program-defined base kind +struct colatitude : kind> {}; +struct azimuth : kind> {}; + +static_assert(Kind); +static_assert(Kind); +static_assert(Kind); +static_assert(Kind); +static_assert(Kind); + +static_assert(!PointKind); +static_assert(!PointKind); +static_assert(!PointKind); +static_assert(!PointKind); +static_assert(!PointKind); + +static_assert(is_same_v); +static_assert(is_same_v); +static_assert(is_same_v, downcast_kind>); + +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); +static_assert(!equivalent); +static_assert(!equivalent); +static_assert(!equivalent); +static_assert(!equivalent); +static_assert(!equivalent); +static_assert(!equivalent>); +static_assert(!equivalent>); + +using radial_area = downcast_kind; // library-defined derived kind +using radial_point = downcast_point_kind; // library-defined base point kind + +static_assert(Kind); +static_assert(!PointKind); + +static_assert(is_same_v); +static_assert(is_same_v); +static_assert(is_same_v>); +static_assert(is_same_v>); +static_assert(is_same_v>); + +static_assert(equivalent); +static_assert(!equivalent); +static_assert(!equivalent); + +static_assert(!Kind); +static_assert(PointKind); + +static_assert(is_same_v); +static_assert(is_same_v); +static_assert(is_same_v>); +static_assert(is_same_v>); + +static_assert(equivalent); +static_assert(!equivalent); +static_assert(!equivalent); + +static_assert(equivalent>); +static_assert(!equivalent); +static_assert(!equivalent); + + +struct width : kind {}; +using horizontal_speed = downcast_kind; + +struct abscissa : point_kind {}; // program-defined base point kind +using horizontal_velocity = downcast_point_kind>; // library-defined derived point kind + +static_assert(!Kind); +static_assert(!Kind); +static_assert(!Kind); +static_assert(PointKind); +static_assert(PointKind); +static_assert(PointKind); + +static_assert(is_same_v); +static_assert(is_same_v); +static_assert(is_same_v, downcast_point_kind>); + +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); +static_assert(!equivalent); +static_assert(!equivalent); + +static_assert(!Kind); +static_assert(PointKind); + +static_assert(is_same_v); +static_assert(is_same_v); +static_assert(is_same_v>); +static_assert(is_same_v>); + +static_assert(equivalent); +static_assert(!equivalent); +static_assert(!equivalent); +static_assert(!equivalent); + +static_assert(!equivalent); +static_assert(!equivalent); + + +struct height : kind {}; + +struct rate_of_climb : derived_kind {}; // program-defined derived kind +struct velocity_of_climb : point_kind {}; // program-defined derived point kind + +static_assert(Kind); +static_assert(Kind); +static_assert(Kind); +static_assert(!PointKind); +static_assert(!PointKind); +static_assert(!PointKind); + +static_assert(is_same_v); +static_assert(is_same_v); +static_assert(is_same_v, downcast_kind>); +static_assert(is_same_v, downcast_kind>); + +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); +static_assert(!equivalent); +static_assert(!equivalent); + +static_assert(!Kind); +static_assert(!Kind); +static_assert(!Kind); +static_assert(PointKind); +static_assert(PointKind); +static_assert(PointKind); + +static_assert(is_same_v); +static_assert(is_same_v); +static_assert(is_same_v, downcast_point_kind>); + +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); +static_assert(equivalent); + +static_assert(!equivalent); +static_assert(!equivalent); +static_assert(!equivalent); + +static_assert(!equivalent); +static_assert(!equivalent); +static_assert(!equivalent); + +static_assert(!equivalent); +static_assert(!equivalent); + +static_assert(is_same_v, downcast_kind>); + +} // namespace diff --git a/test/unit_test/static/quantity_kind_test.cpp b/test/unit_test/static/quantity_kind_test.cpp new file mode 100644 index 00000000..cb10afd5 --- /dev/null +++ b/test/unit_test/static/quantity_kind_test.cpp @@ -0,0 +1,675 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "test_tools.h" +#include "units/chrono.h" +#include "units/physical/si/cgs/derived/speed.h" +#include "units/physical/si/derived/area.h" +#include "units/physical/si/derived/frequency.h" +#include "units/physical/si/derived/speed.h" +#include "units/physical/si/fps/derived/speed.h" +#include "units/quantity_point.h" +#include "units/quantity_kind.h" +#include +#include +#include + +namespace { + +using namespace units; +namespace si = physical::si; +using namespace si; +using namespace unit_constants; + +constexpr auto cgs_cm = cgs::unit_constants::cm; + +using namespace std::chrono_literals; + +struct radius_kind : kind {}; +struct width_kind : kind {}; +struct height_kind : kind {}; + +struct horizontal_area_kind : derived_kind {}; +struct rate_of_climb_kind : derived_kind {}; + +struct apple : kind {}; +struct orange : kind {}; + +struct time_kind : kind {}; + +struct cgs_width_kind : kind {}; + +template using radius = quantity_kind; +template using width = quantity_kind; +template using height = quantity_kind; + +template using horizontal_area = quantity_kind; +template using rate_of_climb = quantity_kind; + +template using apples = quantity_kind; +template using oranges = quantity_kind; + +template using cgs_width = quantity_kind; + +///////////// +// concepts +///////////// + +static_assert(QuantityKind>); +static_assert(QuantityKind>); +static_assert(!QuantityKind); +static_assert(!QuantityKind>); +static_assert(!QuantityKind>); + +static_assert(QuantityKindOf, width_kind>); +static_assert(!QuantityKindOf, height_kind>); +static_assert(!QuantityKindOf, metre>); +static_assert(!QuantityKindOf, width_kind>); +static_assert(!QuantityKindOf, metre>); +static_assert(!QuantityKindOf, width_kind>); +static_assert(!QuantityKindOf, dim_length>); +static_assert(!QuantityKindOf, metre>); + + +/////////////// +// invariants +/////////////// + +static_assert(sizeof(width) == sizeof(double)); +static_assert(sizeof(height) == sizeof(short)); + +template +concept invalid_types = requires { + requires !requires { typename quantity_kind; }; // unit of a different dimension + requires !requires { typename quantity_kind>; }; // quantity used as Rep + requires !requires { typename quantity_kind>; }; // quantity point used as Rep + requires !requires { typename quantity_kind>; }; // quantity kind used as Rep + requires !requires { typename quantity_kind; }; // reordered arguments + requires !requires { typename quantity_kind; }; // reordered arguments +}; +static_assert(invalid_types); + +static_assert(std::is_trivially_default_constructible_v>); +static_assert(std::is_trivially_copy_constructible_v>); +static_assert(std::is_trivially_move_constructible_v>); +static_assert(std::is_trivially_copy_assignable_v>); +static_assert(std::is_trivially_move_assignable_v>); +static_assert(std::is_trivially_destructible_v>); + +static_assert(std::is_nothrow_default_constructible_v>); +static_assert(std::is_nothrow_copy_constructible_v>); +static_assert(std::is_nothrow_move_constructible_v>); +static_assert(std::is_nothrow_copy_assignable_v>); +static_assert(std::is_nothrow_move_assignable_v>); +static_assert(std::is_nothrow_destructible_v>); + +static_assert(std::is_trivially_copyable_v>); +static_assert(std::is_standard_layout_v>); + +static_assert(std::default_initializable>); +static_assert(std::move_constructible>); +static_assert(std::copy_constructible>); +static_assert(std::equality_comparable>); +static_assert(std::totally_ordered>); +static_assert(std::regular>); + +static_assert(std::three_way_comparable>); + +static_assert(!std::is_aggregate_v>); + + +/////////////////// +// member aliases +/////////////////// + +static_assert(is_same_v::kind_type, width_kind>); +static_assert(is_same_v::quantity_type, length>); +static_assert(is_same_v::dimension, dim_length>); +static_assert(is_same_v::unit, metre>); +static_assert(is_same_v::rep, double>); + + +//////////////////// +// common observer +//////////////////// + +static_assert(same(radius{}.common(), length{})); +static_assert(radius{}.common() == // [VIM3] 1.2 kind of quantity + height{}.common()); // aspect common to mutually comparable quantities + // hence `.common()` +static_assert(!std::equality_comparable_with, oranges<>>); + + +//////////////////////////// +// static member functions +//////////////////////////// + +static_assert(width::zero().common() == 0 * m); +static_assert(width::one().common() == 1 * m); +static_assert(width::min().common() == 0 * m); +static_assert(width::max().common() == std::numeric_limits::max() * m); +static_assert(width::min().common().count() == std::numeric_limits::lowest()); +static_assert(width::max().common().count() == std::numeric_limits::max()); + + +//////////////////////// +// default constructor +//////////////////////// + +// default initialization +#if !defined(COMP_MSVC) +static_assert([] { + const auto read_uninitialized_quantity = [] { + width w; + ++w; + }; + return !require_constant_invocation; +}()); +#endif + +// value initialization +static_assert(width{}.common() == 0 * m); + + +///////// +// CTAD +///////// + +static_assert(same(quantity_kind(rate_of_climb(0.01 * km / h)), + rate_of_climb(0.01 * km / h))); + + +//////////////////////////// +// construction from a rep +//////////////////////////// + +static_assert(construct_from_only>(1).common() == 1); +static_assert(construct_from_only>(1.0).common() == 1); +static_assert(construct_from_only>(1ULL).common().count() == 1); +static_assert(construct_from_only>(1.0L).common().count() == 1); +static_assert(!constructible_or_convertible_from>(1.0)); +static_assert(!constructible_or_convertible_from>(1.0)); +static_assert(!constructible_or_convertible_from>(1.0)); +static_assert(!constructible_or_convertible_from>(1.0f)); +static_assert(!constructible_or_convertible_from>(1.0)); +static_assert(!constructible_or_convertible_from>(1)); + + +///////////////////////////////// +// construction from a quantity +///////////////////////////////// + +static_assert(construct_from_only>(1 * m).common() == 1 * m); +static_assert(construct_from_only>(1 * km).common() == 1 * km); +// static_assert(construct_from_only>(1 * cgs_cm).common() == 1 * cm); // TODO: Fix #210 +static_assert(construct_from_only>(1 * cgs_cm).common() == 1 * cm); +static_assert(construct_from_only>(1 * mm).common() == 1 * mm); +static_assert(construct_from_only>(1 * m).common() == 1 * m); +static_assert(construct_from_only>(1 * km).common() == 1 * km); +static_assert(construct_from_only>(1.0 * mm).common() == 1 * mm); +static_assert(construct_from_only>(1.0 * m).common() == 1 * m); +static_assert(construct_from_only>(1.0 * km).common() == 1 * km); +static_assert(construct_from_only>(1.0 * mm).common() == 1 * mm); +static_assert(construct_from_only>(1.0 * m).common() == 1 * m); +static_assert(construct_from_only>(1.0 * km).common() == 1 * km); + +static_assert(!constructible_or_convertible_from>(1 * mm)); +static_assert(!constructible_or_convertible_from>(1.0 * mm)); +static_assert(!constructible_or_convertible_from>(1.0 * m)); +static_assert(!constructible_or_convertible_from>(1.0 * km)); +static_assert(!constructible_or_convertible_from>(1 * s)); +static_assert(!constructible_or_convertible_from>(1 * m * m)); +static_assert(!constructible_or_convertible_from>(1 * m / s)); + + +static_assert(construct_from_only>(1.0f * m).common() == 1 * m); +static_assert(construct_from_only>(short{1} * m).common() == 1 * m); +static_assert(construct_from_only>(1 * m).common() == 1 * m); + + +static_assert(construct_from_only>(quantity(1)).common() == 1); +static_assert(construct_from_only>(dimensionless(1)).common() == 0.01); +static_assert(construct_from_only>(quantity(1.0)).common().count() == 100); +static_assert(construct_from_only>(dimensionless(1)).common().count() == 1); +static_assert(construct_from_only>(quantity(1.0)).common() == 1); +static_assert(construct_from_only>(quantity(1.0f)).common() == 1); +static_assert(construct_from_only>(quantity(1.0)).common() == 1); +static_assert(construct_from_only>(quantity(1)).common() == 1); +static_assert(construct_from_only>(quantity(short{1})).common() == 1); +static_assert(construct_from_only>(quantity(1)).common() == 1); +static_assert(construct_from_only>(quantity(1.0)).common().count() == 1e2); +static_assert(construct_from_only>(quantity(1.0f)).common().count() == 1e2); +static_assert(construct_from_only>(quantity(1.0)).common().count() == 1e2f); +static_assert(construct_from_only>(quantity(1)).common().count() == 1e2); +static_assert(construct_from_only>(quantity(short{1})).common().count() == 1e2); +static_assert(construct_from_only>(quantity(1)).common().count() == 1e2); + +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); +static_assert(!constructible_or_convertible_from>(apples{})); + + +static_assert(construct_from_only>(42s).common() == 42 * s); + + +static_assert(!constructible_or_convertible_from>(1 * s)); +static_assert(!constructible_or_convertible_from>(1 * m * m)); +static_assert(!constructible_or_convertible_from>(1 * m / s)); + + +static_assert(construct_from_only>(1.0 * cgs_cm).common() == 1 * cm); +static_assert(construct_from_only>(1.0 * cm).common() == 1 * cm); + + +//////////////////////////////////////////// +// construction from another quantity kind +//////////////////////////////////////////// + +// clang-format off +static_assert(construct_and_convert_from>(width(1 * m)).common() == 1 * m); +static_assert(construct_and_convert_from>(width(1 * cgs_cm)).common() == 1 * cm); +static_assert(construct_and_convert_from>(width(1 * cgs_cm)).common() == 1 * cm); + +static_assert(construct_and_convert_from>(width(1 * m)).common() == 1 * m); +static_assert(!constructible_or_convertible_from>(width(1.0 * m))); + +static_assert(construct_and_convert_from>(width(1 * km)).common() == 1 * km); +static_assert(!constructible_or_convertible_from>(width(1 * m))); + +static_assert(construct_and_convert_from>(width(1 * km)).common() == 1 * km); +static_assert(construct_and_convert_from>(width(1 * m)).common() == 1 * m); + +static_assert(!constructible_or_convertible_from>(height(1 * m))); +static_assert(!constructible_or_convertible_from>(width(1 * m) / m)); +static_assert(!constructible_or_convertible_from>(oranges(1))); +// clang-format on + + +////////////////////////////////// +// construction from other types +////////////////////////////////// + +// clang-format off +static_assert(!constructible_or_convertible_from>(quantity_point(1 * m))); +static_assert(!constructible_or_convertible_from>(quantity_point(1 * km))); +static_assert(!constructible_or_convertible_from>(quantity_point(1 * m))); +static_assert(!constructible_or_convertible_from>(quantity_point(1 * km))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * m))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * km))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * m * m))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * s))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * s))); +static_assert(!constructible_or_convertible_from>(1s)); +static_assert(!constructible_or_convertible_from>(1.0s)); +static_assert(!constructible_or_convertible_from>(quantity_point(1))); +static_assert(!constructible_or_convertible_from>(quantity_point(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(quantity_point(1))); +static_assert(!constructible_or_convertible_from>(quantity_point(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0))); +static_assert(!constructible_or_convertible_from>(quantity_point(dimensionless(1.0)))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * m))); +static_assert(!constructible_or_convertible_from>(1s)); +static_assert(!constructible_or_convertible_from>(1.0s)); +// clang-format on + + +//////////////////////// +// assignment operator +//////////////////////// + +static_assert((width(2 * m) = width(1 * m)).common() == 1 * m); +static_assert((width(2 * m) = width(1 * km)).common() == 1 * km); +static_assert(!std::is_assignable_v, width>); +static_assert(!std::is_assignable_v, width>); + + +///////////////////// +// member operators +///////////////////// + +#if !defined(COMP_MSVC) || defined(NDEBUG) +static_assert([]() { + width w(1 * m); + assert(+w.common() == 1 * m); + assert(-w.common() == -1 * m); + assert(&++w == &w && w.common() == 2 * m); + assert(&--w == &w && w.common() == 1 * m); + assert((w++).common() == 1 * m && w.common() == 2 * m); + assert((w--).common() == 2 * m && w.common() == 1 * m); + assert(&(w += w) == &w && w.common() == 2 * m); + assert(&(w -= w) == &w && w.common() == 0 * m); + w = width(3 * m); + assert(&(w *= 3) == &w && w.common() == 9 * m); + assert(&(w /= 2) == &w && w.common() == 4 * m); + assert(&(w %= 3) == &w && w.common() == 1 * m); + assert(&(w %= w) == &w && w.common() == 0 * m); + w = width(3 * m); + assert(&(w *= 3.9) == &w && w.common() == 11 * m); + assert(&(w /= 3.9) == &w && w.common() == 2 * m); + assert(&(w %= 3.9) == &w && w.common() == 2 * m); + return true; +}()); +#endif + +static_assert(same((-width(short{1} * m)).common(), int{-1} * m)); + +template +concept invalid_compound_assignments_ = requires(quantity_kind w, Qx q) { + requires !requires { w += q; }; + requires !requires { w -= q; }; + requires !requires { w *= q; }; + requires !requires { w /= q; }; + requires !requires { w %= q; }; +}; +template +concept invalid_compound_assignments = requires(quantity_kind w) { + requires !requires { w += 1; }; + requires !requires { w -= 1; }; + requires !requires { w %= w * 1.0; }; + requires invalid_compound_assignments_>; + requires invalid_compound_assignments_>; + requires invalid_compound_assignments_>; + requires invalid_compound_assignments_>; + requires invalid_compound_assignments_; +}; +static_assert(invalid_compound_assignments); +static_assert(invalid_compound_assignments_); + + +///////////////////////// +// non-member operators +///////////////////////// + +static_assert(same(width(2 * m) + width(3 * m), width(5 * m))); +static_assert(same(width(2 * m) + width(3. * m), width(5. * m))); +static_assert(same(width(2. * m) + width(3 * m), width(5. * m))); +static_assert(same(width(2 * km) + width(3e3 * m), width(5e3 * m))); +static_assert(same(width(2 * m) - width(3 * m), width(-1 * m))); +static_assert(same(width(2 * m) - width(3. * m), width(-1. * m))); +static_assert(same(width(2. * m) - width(3 * m), width(-1. * m))); +static_assert(same(width(2e3 * m) - width(3 * km), width(-1e3 * m))); + +static_assert(!std::is_invocable_v, width, double>); +static_assert(!std::is_invocable_v, width, length>); +static_assert(!std::is_invocable_v, width, quantity_point>); +static_assert(!std::is_invocable_v, width, height>); +static_assert(!std::is_invocable_v, width, double>); +static_assert(!std::is_invocable_v, width, length>); +static_assert(!std::is_invocable_v, width, quantity_point>); +static_assert(!std::is_invocable_v, width, height>); + +// clang-format off +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, day>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind, day>>); +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, day>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind, day>>); +// clang-format on + +static_assert(same(width(2 * m) * 3, width(6 * m))); +static_assert(same(width(2 * m) * 3., width(6. * m))); +static_assert(same(width(2. * m) * 3, width(6. * m))); +static_assert(same(2 * width(3 * m), width(6 * m))); +static_assert(same(2 * width(3. * m), width(6. * m))); +static_assert(same(2. * width(3 * m), width(6. * m))); + +static_assert(same(height(2 * m) * (3 * Hz), rate_of_climb(6 * m / s))); +static_assert(same(height(2 * m) * (3. * Hz), rate_of_climb(6. * m / s))); +static_assert(same(height(2. * m) * (3 * Hz), rate_of_climb(6. * m / s))); +static_assert(same((2 * Hz) * height(3 * m), rate_of_climb(6 * m / s))); +static_assert(same((2 * Hz) * height(3. * m), rate_of_climb(6. * m / s))); +static_assert(same((2. * Hz) * height(3 * m), rate_of_climb(6. * m / s))); + +static_assert(same(width(2 * m) / 3, width(0 * m))); +static_assert(same(width(2 * m) / 3., width(2 / 3. * m))); +static_assert(same(width(2. * m) / 3, width(2. / 3 * m))); + +static_assert(same((2 / width(3 * m)).common(), 2 / 3 / m)); +static_assert(same((2 / width(3. * m)).common(), 2 / 3. / m)); +static_assert(same((2. / width(3 * m)).common(), 2. / 3 / m)); + +static_assert(same(height(2 * m) / (3 * s), rate_of_climb(0 * m / s))); +static_assert(same(height(2 * m) / (3. * s), rate_of_climb(2 / 3. * m / s))); +static_assert(same(height(2. * m) / (3 * s), rate_of_climb(2. / 3 * m / s))); + +static_assert(same(width(2 * m) * dimensionless(3), width(6 * cm))); +static_assert(same(dimensionless(2) * width(3 * m), width(6 * cm))); +static_assert(same(width(2 * m) / dimensionless(3), width(2. / 3 * hm))); +static_assert(same(width(2 * m) % dimensionless(3), width(2 * cm))); + +static_assert(same(((2 * m) / height(3 * m)), quantity_kind, one, int>(0))); +static_assert(same(((2 * m) / height(3 * m)).common(), quantity(0))); +static_assert(same(((2 * m) / height(3. * m)).common(), quantity(2 / 3.))); +static_assert(same(((2. * m) / height(3 * m)).common(), quantity(2. / 3))); +static_assert(same(((2 * m) / height(3 * m) * (0 * m)), height(0 * m))); + +static_assert(same(width(2 * m) % 3, width(2 * m))); +static_assert(same(width(3 * m) % width(2 * m), width(1 * m))); + +static_assert(!std::is_invocable_v, width, width>); +static_assert(!std::is_invocable_v, width, height>); +static_assert(!std::is_invocable_v, height, quantity_point>); +static_assert(!std::is_invocable_v, quantity_point, height>); + +static_assert(!std::is_invocable_v, width, height>); +static_assert(!std::is_invocable_v, height, quantity_point>); +static_assert(!std::is_invocable_v, quantity_point, height>); + +static_assert(!std::is_invocable_v, width, length>); +static_assert(!std::is_invocable_v, width, quantity_point>); +static_assert(!std::is_invocable_v, width, double>); +static_assert(!std::is_invocable_v, width, width>); + +// clang-format off +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, day>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind, day>>); +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, day>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind, day>>); +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, one>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, one>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind< height_kind, metre>>); +static_assert(!std::is_invocable_v, quantity_kind< width_kind, metre>, quantity_kind, day>>); +static_assert(!std::is_invocable_v, quantity_kind, day>, quantity_kind, day>>); +// clang-format on + + +///////////////////////// +// comparison operators +///////////////////////// + +static_assert(width(1 * m) == width(1 * m)); +static_assert(width(1 * m) == width(1.0 * m)); +static_assert(width(1 * m) == width(1000 * mm)); +static_assert(width(1 * m) == width(1e3 * mm)); +static_assert(width(2 * m) != width(1 * m)); +static_assert(width(2 * m) != width(1.0 * cgs_cm)); +static_assert(std::equality_comparable_with, width>); +static_assert(std::equality_comparable_with, width>); +static_assert(std::equality_comparable_with, width>); +static_assert(std::equality_comparable_with, width>); +template +concept invalid_equality = requires(quantity_kind w) { + requires !requires { w == 1; }; + requires !requires { w != 1.0; }; + requires !requires { w == 1 * m; }; + requires !requires { w != 1.0 * cgs_cm; }; + requires !requires { w == 1 * km; }; + requires !requires { w != quantity(1); }; + requires !requires { w == dimensionless(1.0); }; + requires !requires { w != height(1 * m); }; + requires !requires { w == height(1.0 * km); }; + requires !requires { w != horizontal_area(1 * m * m); }; + requires !requires { w == rate_of_climb(1.0 * km / h); }; + requires !requires { w != quantity_point(1 * m); }; + requires !requires { w == quantity_point(1.0 * mm); }; + requires !requires { w != quantity_point(quantity(1)); }; + requires !requires { w == quantity_point(dimensionless(1.0)); }; +}; +static_assert(invalid_equality); + +static_assert(width(1 * m) < width(2 * m)); +static_assert(width(1 * m) <= width(2.0 * m)); +static_assert(width(1 * m) <= width(1 * km)); +static_assert(width(1 * m) >= width(1e3 * mm)); +static_assert(width(2 * m) >= width(1 * mm)); +static_assert(width(2 * m) > width(1 * cgs_cm)); +static_assert(std::three_way_comparable_with, width>); +static_assert(std::three_way_comparable_with, width>); +static_assert(std::three_way_comparable_with, width>); +static_assert(std::three_way_comparable_with, width>); +template +concept invalid_relational = requires(quantity_kind w) { + requires !requires { w < 1; }; + requires !requires { w <= 1.0; }; + requires !requires { w >= 1 * m; }; + requires !requires { w > 1.0 * cgs_cm; }; + requires !requires { w <=> 1 * km; }; + requires !requires { w < quantity(1); }; + requires !requires { w <= dimensionless(1.0); }; + requires !requires { w >= height(1 * m); }; + requires !requires { w > height(1.0 * km); }; + requires !requires { w <=> horizontal_area(1 * m * m); }; + requires !requires { w < rate_of_climb(1.0 * km / h); }; + requires !requires { w <= quantity_point(1 * m); }; + requires !requires { w >= quantity_point(1.0 * mm); }; + requires !requires { w > quantity_point(quantity(1)); }; + requires !requires { w <=> quantity_point(dimensionless(1.0)); }; +}; +static_assert(invalid_relational); + + +/////////////////////// +// quantity_kind_cast +/////////////////////// + +// clang-format off +static_assert(same(quantity_kind_cast>(width(1 * m)), width(1 * m))); +static_assert(same(quantity_kind_cast>(width(1 * m)), width(1.0 * m))); +static_assert(same(quantity_kind_cast>(width(999 * m)), width(0 * km))); +static_assert(same(quantity_kind_cast>(width(1000 * m)), width(1 * km))); +static_assert(same(quantity_kind_cast>(width(999 * m)), width(0.999 * km))); +static_assert(same(quantity_kind_cast(width(1 * m)), width(1.0 * m))); +static_assert(same(quantity_kind_cast(width(1 * m)), width(1 * m))); +static_assert(same(quantity_kind_cast(width(999 * m)), width(0 * km))); +static_assert(same(quantity_kind_cast(width(1000 * m)), width(1 * km))); +static_assert(same(quantity_kind_cast>(width(1 * m)), height(1 * m))); +static_assert(same(quantity_kind_cast>(width(1 * m)), height(1.0 * m))); +static_assert(same(quantity_kind_cast>(width(999 * m)), height(0 * km))); +static_assert(same(quantity_kind_cast>(width(1000 * m)), height(1 * km))); +static_assert(same(quantity_kind_cast>(width(999 * m)), height(0.999 * km))); +static_assert(same(quantity_kind_cast(width(1 * m)), height(1 * m))); +static_assert(same(quantity_kind_cast(width(1 * m)), height(1 * m))); +static_assert(same(quantity_kind_cast(width(999 * m)), height(0 * km))); +static_assert(same(quantity_kind_cast(width(1000 * m)), height(1 * km))); +static_assert(same(quantity_kind_cast>(width(1 * cm)), cgs_width(1 * cgs_cm))); +static_assert(same(quantity_kind_cast(width(1 * cm)), cgs_width(1 * cgs_cm))); +static_assert(same(quantity_kind_cast(width(1 * cm)), cgs_width(1 * cgs_cm))); +static_assert(same(quantity_kind_cast(width(1 * m)), cgs_width(1 * m))); +static_assert(same(quantity_kind_cast(width(1 * m)), cgs_width(1 * m))); +static_assert(same(quantity_kind_cast(width(1 * cm)), width(1 * cgs_cm))); +static_assert(same(quantity_kind_cast>(width(1 * m)), width(0 * km))); +static_assert(same(quantity_kind_cast>(width(1 * m)), width(100 * cm))); +static_assert(same(quantity_kind_cast>(width(0.01 * m)), width(1 * cm))); +static_assert(same(quantity_kind_cast>(width(1 * cgs_cm)), width(1 * cgs_cm))); +// clang-format on +template +concept invalid_cast = requires { + requires !requires { quantity_kind_cast>(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast>(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast>(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast>(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast>(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast>(quantity_kind(1 * m)); }; + requires !requires { quantity_kind_cast>(quantity_kind(1 * m)); }; +}; +static_assert(invalid_cast); + + +///////////////////////// +// extensible interface +///////////////////////// + +namespace mylib { + +struct radius_kind : kind {}; + +struct cylinder_size {}; + +template Radius, units::QuantityKindOf Height> +cylinder_size operator+(Radius, Height); + +} // namespace mylib + +namespace yourapp { + +static_assert(is_same_v(1. * m) + height(1. * m))>); + +} // namespace yourapp + +} // namespace diff --git a/test/unit_test/static/quantity_point_kind_test.cpp b/test/unit_test/static/quantity_point_kind_test.cpp new file mode 100644 index 00000000..7c993901 --- /dev/null +++ b/test/unit_test/static/quantity_point_kind_test.cpp @@ -0,0 +1,654 @@ +// The MIT License (MIT) +// +// Copyright (c) 2018 Mateusz Pusz +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include "test_tools.h" +#include "units/chrono.h" +#include "units/physical/si/cgs/derived/speed.h" +#include "units/physical/si/derived/area.h" +#include "units/physical/si/derived/frequency.h" +#include "units/physical/si/derived/speed.h" +#include "units/physical/si/fps/derived/speed.h" +#include "units/quantity_point.h" +#include "units/quantity_point_kind.h" +#include +#include +#include + +namespace { + +using namespace units; +namespace si = physical::si; +using namespace si; +using namespace unit_constants; + +constexpr auto cgs_cm = cgs::unit_constants::cm; + +using namespace std::chrono_literals; + +struct width_kind : kind {}; +struct height_kind : kind {}; +struct abscissa_kind : point_kind {}; +struct ordinate_kind : point_kind {}; + +struct distance_kind : kind {}; +struct cgs_width_kind : kind {}; +struct cgs_height_kind : kind {}; +struct rate_of_climb_kind : derived_kind {}; +struct altitude_kind : point_kind {}; + +struct apple : kind {}; +struct orange : kind {}; +struct nth_apple_kind : point_kind {}; +struct nth_orange_kind : point_kind {}; + +struct time_kind : kind {}; +struct time_point_kind : point_kind {}; + +template using width = quantity_kind; +template using height = quantity_kind; +template using abscissa = quantity_point_kind; +template using ordinate = quantity_point_kind; + +template using distance = quantity_kind; +template using cgs_width = quantity_kind; +template using cgs_height = quantity_kind; +template using rate_of_climb = quantity_kind; +template using altitude = quantity_point_kind; + +template using apples = quantity_kind; +template using oranges = quantity_kind; +template using nth_apple = quantity_point_kind; +template using nth_orange = quantity_point_kind; + +///////////// +// concepts +///////////// + +static_assert(QuantityPointKind>); +static_assert(QuantityPointKind>); +static_assert(!QuantityPointKind); +static_assert(!QuantityPointKind>); +static_assert(!QuantityPointKind>); +static_assert(!QuantityPointKind>); + +static_assert(QuantityPointKindOf, abscissa_kind>); +static_assert(!QuantityPointKindOf, ordinate_kind>); +static_assert(!QuantityPointKindOf, metre>); +static_assert(!QuantityPointKindOf, abscissa_kind>); +static_assert(!QuantityPointKindOf, metre>); +static_assert(!QuantityPointKindOf, abscissa_kind>); +static_assert(!QuantityPointKindOf, width_kind>); +static_assert(!QuantityPointKindOf, metre>); +static_assert(!QuantityPointKindOf, width_kind>); +static_assert(!QuantityPointKindOf, dim_length>); +static_assert(!QuantityPointKindOf, metre>); + + +/////////////// +// invariants +/////////////// + +static_assert(sizeof(abscissa) == sizeof(double)); +static_assert(sizeof(ordinate) == sizeof(short)); + +template +concept invalid_types = requires { + requires !requires { typename quantity_point_kind; }; // width_kind is not a point kind + requires !requires { typename quantity_point_kind; }; // unit of a different dimension + requires !requires { typename quantity_point_kind>; }; // quantity used as Rep + requires !requires { typename quantity_point_kind>; }; // quantity point used as Rep + requires !requires { typename quantity_point_kind>; }; // quantity kind used as Rep + requires !requires { typename quantity_point_kind>; }; // quantity point kind used as Rep + requires !requires { typename quantity_point_kind; }; // reordered arguments + requires !requires { typename quantity_point_kind; }; // reordered arguments +}; +static_assert(invalid_types); + +static_assert(std::is_trivially_default_constructible_v>); +static_assert(std::is_trivially_copy_constructible_v>); +static_assert(std::is_trivially_move_constructible_v>); +static_assert(std::is_trivially_copy_assignable_v>); +static_assert(std::is_trivially_move_assignable_v>); +static_assert(std::is_trivially_destructible_v>); + +static_assert(std::is_nothrow_default_constructible_v>); +static_assert(std::is_nothrow_copy_constructible_v>); +static_assert(std::is_nothrow_move_constructible_v>); +static_assert(std::is_nothrow_copy_assignable_v>); +static_assert(std::is_nothrow_move_assignable_v>); +static_assert(std::is_nothrow_destructible_v>); + +static_assert(std::is_trivially_copyable_v>); +static_assert(std::is_standard_layout_v>); + +static_assert(std::default_initializable>); +static_assert(std::move_constructible>); +static_assert(std::copy_constructible>); +static_assert(std::equality_comparable>); +static_assert(std::totally_ordered>); +static_assert(std::regular>); + +static_assert(std::three_way_comparable>); + +static_assert(!std::is_aggregate_v>); + + +/////////////////// +// member aliases +/////////////////// + +static_assert(is_same_v::point_kind_type, abscissa_kind>); +static_assert(is_same_v::kind_type, width_kind>); +static_assert(is_same_v::quantity_kind_type, width>); +static_assert(is_same_v::quantity_type, length>); +static_assert(is_same_v::dimension, dim_length>); +static_assert(is_same_v::unit, metre>); +static_assert(is_same_v::rep, double>); + + +////////////////////// +// relative observer +////////////////////// + +static_assert(same(abscissa{}.relative(), width{})); + + +//////////////////////////// +// static member functions +//////////////////////////// + +static_assert(abscissa::min().relative().common() == 0 * m); +static_assert(abscissa::max().relative().common() == std::numeric_limits::max() * m); +static_assert(abscissa::min().relative().common().count() == std::numeric_limits::lowest()); +static_assert(abscissa::max().relative().common().count() == std::numeric_limits::max()); + + +//////////////////////// +// default constructor +//////////////////////// + +// default initialization +#if !defined(COMP_MSVC) +static_assert([] { + const auto read_uninitialized_quantity = [] { + abscissa w; + ++w; + }; + return !require_constant_invocation; +}()); +#endif + +// value initialization +static_assert(abscissa{}.relative().common() == 0 * m); + + +///////// +// CTAD +///////// + +static_assert(same(quantity_point_kind(width(0 * m)), abscissa{})); +static_assert(same(quantity_point_kind(abscissa(0 * m)), abscissa{})); + + +//////////////////////////// +// construction from a rep +//////////////////////////// + +static_assert(construct_from_only>(1.0).relative().common() == 1); +static_assert(construct_from_only>(1.0f).relative().common() == 1); +static_assert(construct_from_only>(1).relative().common() == 1); +static_assert(construct_from_only>(short{1}).relative().common() == 1); +static_assert(construct_from_only>(1).relative().common() == 1); +static_assert(construct_from_only>(1).relative().common() == 1); +static_assert(construct_from_only>(one_rep{}).relative().common() == 1); +static_assert(construct_from_only>(one_rep{}).relative().common() == 1); +static_assert(construct_from_only>(one_rep{}).relative().common() == 1); +static_assert(construct_from_only>(1ULL).relative().common().count() == 1); +static_assert(construct_from_only>(1).relative().common().count() == 1); +static_assert(!constructible_or_convertible_from>(1.0)); +static_assert(!constructible_or_convertible_from>(1.0)); +static_assert(!constructible_or_convertible_from>(1.0)); +static_assert(!constructible_or_convertible_from>(1.0f)); +static_assert(!constructible_or_convertible_from>(1)); +static_assert(!constructible_or_convertible_from>(short{1})); +static_assert(!constructible_or_convertible_from>(1)); + + +///////////////////////////////// +// construction from a quantity +///////////////////////////////// + +// clang-format off +static_assert(construct_from_only>(1 * m).relative().common() == 1 * m); +static_assert(construct_from_only>(1 * m).relative().common() == 1 * m); +static_assert(construct_from_only>(1 * km).relative().common() == 1 * km); +static_assert(construct_from_only>(1ULL * m).relative().common() == 1 * m); +// static_assert(construct_from_only>(1 * cgs_cm).relative().common() == 1 * cm); // TODO: Fix #210 +static_assert(construct_from_only>(1 * m).relative().common() == 1 * m); +static_assert(construct_from_only>(1.0 * km).relative().common() == 1 * km); +static_assert(construct_from_only>(1 * cgs_cm).relative().common() == 1 * cm); +static_assert(construct_from_only>(1.0 * cgs_cm).relative().common() == 1 * cm); +static_assert(!constructible_or_convertible_from>(1 * mm)); +static_assert(!constructible_or_convertible_from>(1.0 * m)); +static_assert(!constructible_or_convertible_from>(1.0 * km)); +static_assert(!constructible_or_convertible_from>(1 * cgs_cm)); +static_assert(!constructible_or_convertible_from>(quantity(1))); +static_assert(!constructible_or_convertible_from>(1 * s)); +static_assert(!constructible_or_convertible_from>(1s)); + +static_assert(construct_from_only>(quantity(1)).relative().common() == 1); +static_assert(construct_from_only>(dimensionless(1)).relative().common() == 0.01); +static_assert(construct_from_only>(dimensionless(1)).relative().common() == 0.01); +static_assert(construct_from_only>(dimensionless(1)).relative().common().count() == 1); +static_assert(construct_from_only>(quantity(1)).relative().common().count() == 100); +static_assert(!constructible_or_convertible_from>(quantity(1.0))); +static_assert(!constructible_or_convertible_from>(dimensionless(1))); +static_assert(!constructible_or_convertible_from>(quantity(1.0))); +static_assert(!constructible_or_convertible_from>(dimensionless(1))); +static_assert(!constructible_or_convertible_from>(1 * m)); +static_assert(!constructible_or_convertible_from>(1 * s)); +static_assert(!constructible_or_convertible_from>(1s)); +// clang-format on + + +/////////////////////////////////////// +// construction from a quantity point +/////////////////////////////////////// + +static_assert(construct_from_only>(1 * m).relative().common() == 1 * m); +static_assert(construct_from_only>(quantity_point(short{1} * m)).relative().common() == 1 * m); +static_assert(construct_from_only>(quantity_point(1 * m)).relative().common() == 1 * m); +static_assert(construct_from_only>(quantity_point(1 * km)).relative().common() == 1 * km); +static_assert(construct_from_only>(quantity_point(1 * m)).relative().common() == 1 * m); +static_assert(construct_from_only>(quantity_point(1 * km)).relative().common() == 1 * km); +static_assert(construct_from_only>(quantity_point(1.0 * m)).relative().common() == 1 * m); +static_assert(construct_from_only>(quantity_point(1.0 * mm)).relative().common() == 1 * mm); +static_assert(!constructible_or_convertible_from>(quantity_point(1 * mm))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * m))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * km))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * m * m))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * s))); + +// clang-format off +static_assert(construct_from_only>(quantity_point(1)).relative().common() == 1); +static_assert(construct_from_only>(quantity_point(1)).relative().common() == 1); +static_assert(construct_from_only>(quantity_point(1)).relative().common() == 1); +static_assert(construct_from_only>(quantity_point(dimensionless(1))).relative().common() == 0.01); +static_assert(construct_from_only>(quantity_point(1.0)).relative().common() == 1); +static_assert(construct_from_only>(quantity_point(dimensionless(1.0))).relative().common() == 0.01); +static_assert(construct_from_only>(quantity_point(1)).relative().common().count() == 100); +static_assert(construct_from_only>(quantity_point(dimensionless(1))).relative().common().count() == 1); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0))); +static_assert(!constructible_or_convertible_from>(quantity_point(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0))); +static_assert(!constructible_or_convertible_from>(quantity_point(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(quantity_point(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(quantity_point(1.0 * s))); +// clang-format on + + +////////////////////////////////////// +// construction from a quantity kind +////////////////////////////////////// + +// clang-format off +static_assert(construct_from_only>(width(1 * m)).relative().common() == 1 * m); +static_assert(construct_from_only>(width(1ULL * km)).relative().common() == 1 * km); +static_assert(construct_from_only>(width(1 * cgs_cm)).relative().common() == 1 * cm); +static_assert(construct_from_only>(width(1 * cgs_cm)).relative().common() == 1 * cm); +static_assert(construct_from_only>(width(1 * m)).relative().common() == 1 * m); +static_assert(construct_from_only>(width(1.0 * mm)).relative().common() == 1 * mm); +static_assert(construct_from_only>(width(1ULL * km)).relative().common() == 1 * km); +static_assert(!constructible_or_convertible_from>(width(1.0 * m))); +static_assert(!constructible_or_convertible_from>(width(1 * mm))); +static_assert(!constructible_or_convertible_from>(height(1 * m))); +static_assert(!constructible_or_convertible_from>(abscissa_kind{}, width(1.0 * m))); +static_assert(!constructible_or_convertible_from>(abscissa_kind{}, width(1 * mm))); +static_assert(!constructible_or_convertible_from>(abscissa_kind{}, height(1 * m))); + +static_assert(construct_from_only>(apples(1)).relative().common() == 1); +static_assert(construct_from_only>(apples(dimensionless(1))).relative().common() == 0.01); +static_assert(construct_from_only>(apples(1)).relative().common().count() == 100); +static_assert(construct_from_only>(apples(dimensionless(1))).relative().common().count() == 1); +static_assert(!constructible_or_convertible_from>(apples(1.0))); +static_assert(!constructible_or_convertible_from>(apples(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(apples(1.0))); +static_assert(!constructible_or_convertible_from>(apples(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(width(1 * m) / m)); +static_assert(!constructible_or_convertible_from>(oranges(1))); +// clang-format on + + +////////////////////////////////////////////////// +// construction from another quantity point kind +////////////////////////////////////////////////// + +// clang-format off +static_assert(construct_and_convert_from>(abscissa(1 * m)).relative().common() == 1 * m); +static_assert(construct_and_convert_from>(abscissa(1ULL * km)).relative().common() == 1 * km); +static_assert(construct_and_convert_from>(abscissa(1ULL * m)).relative().common() == 1 * m); +static_assert(construct_and_convert_from>(abscissa(1 * cgs_cm)).relative().common() == 1 * cm); +static_assert(construct_and_convert_from>(abscissa(1 * cgs_cm)).relative().common() == 1 * cm); +static_assert(!constructible_or_convertible_from>(abscissa(1.0 * m))); +static_assert(!constructible_or_convertible_from>(abscissa(1 * m))); +static_assert(!constructible_or_convertible_from>(ordinate(1 * m))); +static_assert(!constructible_or_convertible_from>(quantity_point_kind(1 * s))); + +static_assert(construct_and_convert_from>(nth_apple(1)).relative().common() == 1); +static_assert(construct_and_convert_from>(nth_apple(dimensionless(1))).relative().common() == 0.01); +static_assert(construct_and_convert_from>(nth_apple(1)).relative().common().count() == 100); +static_assert(construct_and_convert_from>(nth_apple(dimensionless(1))).relative().common().count() == 1); +static_assert(!constructible_or_convertible_from>(nth_apple(1.0))); +static_assert(!constructible_or_convertible_from>(nth_apple(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(nth_apple(1.0))); +static_assert(!constructible_or_convertible_from>(nth_apple(dimensionless(1)))); +static_assert(!constructible_or_convertible_from>(nth_orange(1))); +static_assert(!constructible_or_convertible_from>(abscissa(1 * m))); +// clang-format on + + +////////////////////// +// other conversions +////////////////////// + +static_assert(!std::is_convertible_v, int>); +static_assert(!std::is_convertible_v, dimensionless>); +static_assert(!std::is_convertible_v, length>); +static_assert(!std::is_convertible_v, width>); +static_assert(!std::is_convertible_v, height>); +static_assert(!std::is_convertible_v, quantity_point>); + + +//////////////////////// +// assignment operator +//////////////////////// + +// clang-format off +static_assert((abscissa(2 * m) = abscissa(1 * m)).relative().common() == 1 * m); +static_assert((abscissa(2 * m) = abscissa(1 * km)).relative().common() == 1 * km); +static_assert(!std::is_assignable_v, abscissa>); +static_assert(!std::is_assignable_v, abscissa>); +// clang-format on + + +///////////////////// +// member operators +///////////////////// + +#if !defined(COMP_MSVC) || defined(NDEBUG) +static_assert([]() { + const width w(1 * m); + quantity_point_kind x(w); + assert(&++x == &x && x.relative().common() == 2 * m); + assert(&--x == &x && x.relative().common() == 1 * m); + assert((x++).relative().common() == 1 * m && x.relative().common() == 2 * m); + assert((x--).relative().common() == 2 * m && x.relative().common() == 1 * m); + assert(&(x += w) == &x && x.relative().common() == 2 * m); + assert(&(x -= w) == &x && x.relative().common() == 1 * m); + return true; +}()); +#endif + +template +concept invalid_compound_assignments_ = requires(quantity_point_kind x, Qx q) { + requires !requires { x += q; }; + requires !requires { x -= q; }; +}; +template +concept invalid_compound_assignments = requires(quantity_point_kind x) { + requires !requires { x += 1; }; + requires !requires { x -= 1; }; + requires invalid_compound_assignments_>; + requires invalid_compound_assignments_>; + requires invalid_compound_assignments_>; + requires invalid_compound_assignments_>; + requires invalid_compound_assignments_; +}; +static_assert(invalid_compound_assignments); +static_assert(invalid_compound_assignments_); +#if __cpp_lib_chrono >= 201907L +static_assert(invalid_compound_assignments_); +#endif + + +///////////////////////// +// non-member operators +///////////////////////// + +// clang-format off +static_assert(same(abscissa(2 * m) + width(3 * m), abscissa(5 * m))); +static_assert(same(abscissa(2 * m) + width(3. * m), abscissa(5. * m))); +static_assert(same(abscissa(2. * m) + width(3 * m), abscissa(5. * m))); +static_assert(same(abscissa(2 * km) + width(3e3 * m), abscissa(5e3 * m))); +static_assert(same(abscissa(2e3 * m) + width(3 * km), abscissa(5e3 * m))); +static_assert(same(width(2 * m) + abscissa(3 * m), abscissa(5 * m))); +static_assert(same(width(2 * m) + abscissa(3. * m), abscissa(5. * m))); +static_assert(same(width(2. * m) + abscissa(3 * m), abscissa(5. * m))); +static_assert(same(width(2 * km) + abscissa(3e3 * m), abscissa(5e3 * m))); +static_assert(same(width(2e3 * m) + abscissa(3 * km), abscissa(5e3 * m))); +static_assert(!std::is_invocable_v, abscissa, double>); +static_assert(!std::is_invocable_v, abscissa, length>); +static_assert(!std::is_invocable_v, abscissa, quantity_point>); +static_assert(!std::is_invocable_v, abscissa, height>); +static_assert(!std::is_invocable_v, abscissa, abscissa>); +static_assert(!std::is_invocable_v, abscissa, abscissa>); +static_assert(!std::is_invocable_v, abscissa, abscissa>); +static_assert(!std::is_invocable_v, height, abscissa>); +static_assert(!std::is_invocable_v, quantity_point, abscissa>); +static_assert(!std::is_invocable_v, length, abscissa>); +static_assert(!std::is_invocable_v, double, abscissa>); + +static_assert(same(abscissa(2 * m) - width(3 * m), abscissa(-1 * m))); +static_assert(same(abscissa(2 * m) - width(3. * m), abscissa(-1. * m))); +static_assert(same(abscissa(2. * m) - width(3 * m), abscissa(-1. * m))); +static_assert(same(abscissa(2 * km) - width(3e3 * m), abscissa(-1e3 * m))); +static_assert(same(abscissa(2e3 * m) - width(3 * km), abscissa(-1e3 * m))); +static_assert(same(abscissa(2 * m) - abscissa(3 * m), width(-1 * m))); +static_assert(same(abscissa(2 * m) - abscissa(3. * m), width(-1. * m))); +static_assert(same(abscissa(2. * m) - abscissa(3 * m), width(-1. * m))); +static_assert(same(abscissa(2 * km) - abscissa(3e3 * m), width(-1e3 * m))); +static_assert(same(abscissa(2e3 * m) - abscissa(3 * km), width(-1e3 * m))); +static_assert(!std::is_invocable_v, abscissa, double>); +static_assert(!std::is_invocable_v, abscissa, length>); +static_assert(!std::is_invocable_v, abscissa, quantity_point>); +static_assert(!std::is_invocable_v, abscissa, height>); +static_assert(!std::is_invocable_v, abscissa, ordinate>); +static_assert(!std::is_invocable_v, ordinate, abscissa>); +static_assert(!std::is_invocable_v, height, abscissa>); +static_assert(!std::is_invocable_v, quantity_point, abscissa>); +static_assert(!std::is_invocable_v, length, abscissa>); +static_assert(!std::is_invocable_v, double, abscissa>); +// clang-format on + + +///////////////////////// +// comparison operators +///////////////////////// + +// clang-format off +static_assert(abscissa(1 * m) == abscissa(1 * m)); +static_assert(abscissa(1 * m) == abscissa(1.0 * m)); +static_assert(abscissa(1 * m) == abscissa(1000 * mm)); +static_assert(abscissa(1 * m) == abscissa(1e3 * mm)); +static_assert(abscissa(2 * m) != abscissa(1 * m)); +static_assert(abscissa(2 * m) != abscissa(1.0 * cgs_cm)); +static_assert(std::equality_comparable_with, abscissa>); +static_assert(std::equality_comparable_with, abscissa>); +static_assert(std::equality_comparable_with, abscissa>); +static_assert(std::equality_comparable_with, abscissa>); +// clang-format on +template +concept invalid_equality = requires(quantity_point_kind x) { + requires !requires { x == 1; }; + requires !requires { x != 1.0; }; + requires !requires { x == 1 * m; }; + requires !requires { x != 1.0 * cgs_cm; }; + requires !requires { x == 1 * km; }; + requires !requires { x != quantity(1); }; + requires !requires { x == dimensionless(1.0); }; + requires !requires { x != width(1 * m); }; + requires !requires { x == width(1.0 * km); }; + requires !requires { x != height(1 * m); }; + requires !requires { x == height(1.0 * km); }; + requires !requires { x == rate_of_climb(1.0 * km / h); }; + requires !requires { x != quantity_point(1 * m); }; + requires !requires { x == quantity_point(1.0 * mm); }; + requires !requires { x != quantity_point(quantity(1)); }; + requires !requires { x == quantity_point(dimensionless(1.0)); }; + requires !requires { x != quantity_point_kind(cgs_width(1 * m)); }; + requires !requires { x == ordinate(1 * m); }; +}; +static_assert(invalid_equality); + +// clang-format off +static_assert(abscissa(1 * m) < abscissa(2 * m)); +static_assert(abscissa(1 * m) <= abscissa(2.0 * m)); +static_assert(abscissa(1 * m) <= abscissa(1 * km)); +static_assert(abscissa(1 * m) >= abscissa(1e3 * mm)); +static_assert(abscissa(2 * m) >= abscissa(1 * mm)); +static_assert(abscissa(2 * m) > abscissa(1 * cgs_cm)); +static_assert(std::three_way_comparable_with, abscissa>); +static_assert(std::three_way_comparable_with, abscissa>); +static_assert(std::three_way_comparable_with, abscissa>); +static_assert(std::three_way_comparable_with, abscissa>); +// clang-format on +template +concept invalid_relational = requires(quantity_point_kind x) { + requires !requires { x < 1; }; + requires !requires { x <= 1.0; }; + requires !requires { x >= 1 * m; }; + requires !requires { x > 1.0 * cgs_cm; }; + requires !requires { x <=> 1 * km; }; + requires !requires { x < quantity(1); }; + requires !requires { x <= dimensionless(1.0); }; + requires !requires { x >= width(1 * m); }; + requires !requires { x > width(1.0 * km); }; + requires !requires { x <=> height(1 * m); }; + requires !requires { x < height(1.0 * km); }; + requires !requires { x <= rate_of_climb(1.0 * km / h); }; + requires !requires { x >= quantity_point(1 * m); }; + requires !requires { x > quantity_point(1.0 * mm); }; + requires !requires { x <=> quantity_point(quantity(1)); }; + requires !requires { x < quantity_point(dimensionless(1.0)); }; + requires !requires { x <= quantity_point_kind(cgs_width(1 * m)); }; + requires !requires { x >= ordinate(1 * m); }; +}; +static_assert(invalid_relational); + + +///////////////////////////// +// quantity_point_kind_cast +///////////////////////////// + +// clang-format off +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), abscissa(1 * m))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), abscissa(1.0 * m))); +static_assert(same(quantity_point_kind_cast>(abscissa(999 * m)), abscissa(0 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1000 * m)), abscissa(1 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(999 * m)), abscissa(0.999 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), abscissa(1 * m))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), abscissa(1.0 * m))); +static_assert(same(quantity_point_kind_cast>(abscissa(999 * m)), abscissa(0 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1000 * m)), abscissa(1 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(999 * m)), abscissa(0.999 * km))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * m)), abscissa(1.0 * m))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * m)), abscissa(1 * m))); +static_assert(same(quantity_point_kind_cast(abscissa(999 * m)), abscissa(0 * km))); +static_assert(same(quantity_point_kind_cast(abscissa(1000 * m)), abscissa(1 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), ordinate(1 * m))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), ordinate(1.0 * m))); +static_assert(same(quantity_point_kind_cast>(abscissa(999 * m)), ordinate(0 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1000 * m)), ordinate(1 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(999 * m)), ordinate(0.999 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), ordinate(1 * m))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), ordinate(1.0 * m))); +static_assert(same(quantity_point_kind_cast>(abscissa(999 * m)), ordinate(0 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1000 * m)), ordinate(1 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(999 * m)), ordinate(0.999 * km))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * m)), ordinate(1 * m))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * m)), ordinate(1 * m))); +static_assert(same(quantity_point_kind_cast(abscissa(999 * m)), ordinate(0 * km))); +static_assert(same(quantity_point_kind_cast(abscissa(1000 * m)), ordinate(1 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * cm)), quantity_point_kind(cgs_width(1 * cgs_cm)))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * cm)), quantity_point_kind(cgs_width(1 * cgs_cm)))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * cm)), altitude(1 * cgs_cm))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * cm)), altitude(1 * cgs_cm))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * m)), quantity_point_kind(cgs_width(1 * m)))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * m)), altitude(1 * m))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * m)), altitude(1 * m))); +static_assert(same(quantity_point_kind_cast(abscissa(1 * cm)), abscissa(1 * cgs_cm))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), abscissa(0 * km))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * m)), abscissa(100 * cm))); +static_assert(same(quantity_point_kind_cast>(abscissa(0.01 * m)), abscissa(1 * cm))); +static_assert(same(quantity_point_kind_cast>(abscissa(1 * cgs_cm)), abscissa(1 * cgs_cm))); +// clang-format on +template +concept invalid_cast = requires(Int i) { + requires !requires { quantity_point_kind_cast>(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast>(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast>(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast>(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast>(abscissa(i * m)); }; + requires !requires { quantity_point_kind_cast>(abscissa(i * m)); }; +}; +static_assert(invalid_cast); + + +///////////////////////// +// extensible interface +///////////////////////// + +namespace mylib { + +struct width_kind : kind {}; +struct height_kind : kind {}; +struct abscissa_kind : point_kind {}; +struct ordinate_kind : point_kind {}; + +struct point {}; + +template Abscissa, units::QuantityPointKindOf Ordinate> +point operator+(Abscissa, Ordinate); + +} // namespace mylib + +namespace yourapp { + +static_assert(is_same_v(1 * m)) + + quantity_point_kind(quantity_kind(1 * m)))>); + +} // namespace yourapp + +} // namespace diff --git a/test/unit_test/static/quantity_point_test.cpp b/test/unit_test/static/quantity_point_test.cpp index 36a10bc1..5acf737d 100644 --- a/test/unit_test/static/quantity_point_test.cpp +++ b/test/unit_test/static/quantity_point_test.cpp @@ -22,18 +22,20 @@ #include "units/quantity_point.h" #include "test_tools.h" +#include "units/chrono.h" #include "units/math.h" #include "units/physical/si/derived/area.h" #include "units/physical/si/derived/speed.h" #include "units/physical/si/derived/volume.h" #include "units/physical/si/us/base/length.h" -#include #include namespace { using namespace units; -using namespace units::physical::si; +using namespace physical::si; +using namespace unit_constants; +using namespace std::chrono_literals; // class invariants @@ -55,9 +57,17 @@ static_assert(is_same_v::rep, int>); static_assert(is_same_v::rep, double>); static_assert(is_same_v::unit, metre>); static_assert(is_same_v::unit, kilometre>); +static_assert(is_same_v::dimension, dim_length>); +static_assert(is_same_v::quantity_type, quantity>); // constructors +static_assert(quantity_point(1).relative() == quantity(1)); +static_assert(!std::is_convertible_v>); + +static_assert(quantity_point(42s).relative() == 42 * s); +static_assert(!std::is_convertible_v>); + static_assert(quantity_point().relative() == 0_q_m); constexpr quantity_point km{1000_q_m}; static_assert(km.relative() == 1000_q_m); diff --git a/test/unit_test/static/quantity_test.cpp b/test/unit_test/static/quantity_test.cpp index 81512094..050187cd 100644 --- a/test/unit_test/static/quantity_test.cpp +++ b/test/unit_test/static/quantity_test.cpp @@ -220,7 +220,7 @@ static_assert(!std::constructible_from, length, length>); // truncating metre -> kilometre not allowed // converting to double always OK -static_assert(std::constructible_from, length>); +static_assert(std::constructible_from, length>); static_assert(std::convertible_to, length>); static_assert(std::constructible_from, length>); static_assert(std::convertible_to, length>); diff --git a/test/unit_test/static/test_tools.h b/test/unit_test/static/test_tools.h index e4f7975e..47a8463e 100644 --- a/test/unit_test/static/test_tools.h +++ b/test/unit_test/static/test_tools.h @@ -23,6 +23,9 @@ #pragma once #include "units/bits/equivalent.h" +#include +#include +#include template inline constexpr bool compare_impl = false; @@ -36,3 +39,87 @@ inline constexpr bool compare_impl = units::equivalent; template inline constexpr bool compare = compare_impl, std::remove_cvref_t>; + +template +constexpr bool constructible_from(Vs...) { + return std::constructible_from; +} + +template +concept convertible_from_ = requires(Us... us) { [](T) {}({us...}); }; + +template +constexpr bool convertible_from(Vs...) { + if constexpr (sizeof...(Us) + sizeof...(Vs) == 1) + return std::is_convertible_v; + else + return convertible_from_; +} + +template +constexpr bool constructible_or_convertible_from(Vs...) { + return constructible_from() || convertible_from(); +} + +template +constexpr bool constructible_and_convertible_from(Vs...) { + return constructible_from() && convertible_from(); +} + +template + requires (constructible_from()) +constexpr T construct_from(Us... us) { + return T(us...); +} + +template + requires (convertible_from()) +constexpr T convert_from(Us... us) { + if constexpr (sizeof...(Us) == 1) + return [](T t) { return t; }(us...); + else + return {us...}; +} + +template + requires (constructible_from() && convertible_from()) +constexpr T construct_and_convert_from(Us... us) { + T t{construct_from(us...)}; + assert(t == convert_from(us...)); + return t; +} + +template + requires (constructible_from() && !convertible_from()) +constexpr T construct_from_only(Us... us) { + return construct_from(us...); +} + +#if !defined(COMP_GCC) +template typename T, typename = std::void_t<>, typename... Us> +concept ctad_constructible_from_ = requires(Us... us) { T(us...); }; +#else +template typename T, typename = std::void_t<>, typename... Us> +inline constexpr bool ctad_constructible_from_ = false; + +template typename T, typename... Us> +inline constexpr bool ctad_constructible_from_, Us...> = true; +#endif + +template typename T, typename... Us, typename... Vs> +constexpr bool ctad_constructible_from(Vs...) { + return ctad_constructible_from_; +} + +#if UNITS_DOWNCAST_MODE +constexpr auto same = [](T l, T r) { return l == r; }; +#else +constexpr auto same = [](T l, U r) + requires requires { l == r; } + // requires std::equality_comparable_with // TODO: Fix #205 + { return l == r; }; +#endif + +template + requires requires { F(); } +inline constexpr bool require_constant_invocation = requires { new int[1][(F(), 1)]; }; diff --git a/test/unit_test/static/unit_test.cpp b/test/unit_test/static/unit_test.cpp index be223c56..169f21f0 100644 --- a/test/unit_test/static/unit_test.cpp +++ b/test/unit_test/static/unit_test.cpp @@ -51,6 +51,8 @@ struct metre_per_second : unit {}; struct dim_speed : derived_dimension, units::exponent> {}; struct kilometre_per_hour : deduced_unit {}; +static_assert(equivalent); +static_assert(equivalent); static_assert(compare>, metre>); static_assert(compare>, centimetre>); static_assert(compare>, yard>);