Skip to content

Commit fdb7696

Browse files
committed
merge branch 'unit_strings' into 'main'
2 parents db8d781 + a6e73b7 commit fdb7696

File tree

7 files changed

+615
-208
lines changed

7 files changed

+615
-208
lines changed

.github/workflows/cmake.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
cmake-build-type: [Debug, Release]
1717

1818
steps:
19-
- uses: actions/checkout@v2
19+
- uses: actions/checkout@v3
2020

2121
- name: Get doctest
2222
run: git clone https://github.com/onqtam/doctest.git doctest

changes.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Changes
22
=======
33

4+
0.9
5+
* added ability to print known quantity types ("km")
6+
7+
0.8
8+
(dead branch, no further deveulopment)
9+
410
0.7.9
511
* bugfix: only enable unlib::min/max if the scales of their arguments differ
612

common.hpp

Lines changed: 207 additions & 47 deletions
Large diffs are not rendered by default.

quantity.hpp

Lines changed: 229 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
*/
1313

1414
#include <cstdint>
15-
#include <ostream>
1615
#include <istream>
1716
#include <limits>
17+
#include <ostream>
18+
#include <string>
1819
#include <type_traits>
1920

2021
#include <unlib/unit.hpp>
@@ -510,36 +511,6 @@ constexpr auto operator!=(const quantity<U1,S1,V1,T1>& lhs, const quantity<U2,S2
510511
}
511512
/** @} */
512513

513-
/**
514-
* @brief Stream output operator
515-
*
516-
* Streams the stored value into os.
517-
*
518-
* @param os the stream
519-
* @param q the quantity
520-
* @return os
521-
*/
522-
template<typename U, typename S, typename V, typename T>
523-
std::ostream& operator<<(std::ostream& os, const quantity<U, S, V, T>& q) {return os << q.get();}
524-
525-
/**
526-
* @brief Stream input operator
527-
*
528-
* Reads from the stream into q.
529-
*
530-
* @param is the stream
531-
* @param q the quantity
532-
*
533-
* @return is
534-
*/
535-
template<typename U, typename S, typename V, typename T>
536-
std::istream& operator>>(std::istream& is, quantity<U, S, V, T>& q) {
537-
V v;
538-
if (is >> v)
539-
q = quantity<U, S, V, T>{v};
540-
return is;
541-
}
542-
543514
/**
544515
* @{
545516
* @brief Non-member binary arithmetic operators
@@ -738,6 +709,233 @@ template<typename NewQuantity, typename U, typename S, typename V, typename T>
738709
constexpr NewQuantity quantity_cast(const quantity<U,S,V,T>& q) {return NewQuantity{quantity_cast(q)};}
739710
/** @} */
740711

712+
/* I/O ***********************************************************************/
713+
714+
namespace literals {
715+
716+
/**
717+
* Specializations of this template define the prefixes applied to unit
718+
* strings for different scaling (e.g., "k" for kilo or "M" for mega).
719+
* Prefixes for all ISO scalings are pre-defined using the @sq
720+
* UNLIB_DEFINE_SCALE_LITERAL_TRAITS() macro.
721+
*
722+
* Except when inventing a proprietary scaling, users should have no need to
723+
* define their own specializations of this template. Usually it's better to
724+
* define a @sq quantity_traits specialization.
725+
*
726+
* @tparam S Scaling to define a prefix string for
727+
*/
728+
template<typename S>
729+
struct scaling_traits {
730+
using is_specialized = std::false_type;
731+
};
732+
733+
/** Find out whether scaling_traits is specialized for a specific scaling */
734+
template<typename S>
735+
using is_scaling_string_specialized = typename scaling_traits<S>::is_specialized;
736+
737+
/**
738+
* Specializations of this template define the unit strings for different
739+
* units (like "m" for meter). Units who invent their own units can
740+
* specialize this to define unit strings for those.
741+
*
742+
* @note Specializations of this must have a static function `get_string()`.
743+
* (The @sa UNLIB_DEFINE_UNIT_LITERAL_TRAITS() macro is a convenient way
744+
* to create fully conforming specializations.)
745+
*
746+
* @tparam U Unit to define string for
747+
* @tparam T Tag to define string for
748+
*/
749+
template<typename U, typename T>
750+
struct unit_traits {
751+
using is_specialized = std::false_type;
752+
};
753+
754+
/** Find out whether unit_traits is specialized for a specific unit and tag */
755+
template<typename U, typename T>
756+
using is_unit_string_specialized = typename unit_traits<U,T>::is_specialized;
757+
758+
759+
namespace detail {
760+
761+
/* a C++14 static string. */
762+
template<std::size_t StrLen>
763+
struct static_string {
764+
char array[StrLen+1];
765+
};
766+
767+
/* a C++14 constexpr string copy */
768+
template<std::size_t StrSize>
769+
inline constexpr auto copy_static_string(char* to, const char (&from)[StrSize]) {
770+
for(std::size_t idx=0; idx<StrSize-1; ++idx)
771+
to[idx] = from[idx];
772+
return to + StrSize-1;
773+
}
774+
775+
/* constexpr-creating the unit string for a quantity */
776+
template<std::size_t QuantityStrLen, std::size_t ScalingStrSize, std::size_t UnitStrSize>
777+
constexpr auto make_quantity_string( const char (&scaling_str)[ScalingStrSize]
778+
, const char (& unit_str)[ UnitStrSize] ) {
779+
static_string<QuantityStrLen> quantity_str{};
780+
char* it = quantity_str.array;
781+
it = copy_static_string(it, scaling_str);
782+
copy_static_string(it, unit_str);
783+
return quantity_str;
784+
}
785+
786+
template<typename U, typename S, typename T>
787+
struct quantity_string_traits {
788+
using unit_type = U;
789+
using scale_type = S;
790+
using tag_type = T;
791+
792+
using scaling_tr = scaling_traits<scale_type>;
793+
using unit_tr = unit_traits<unit_type, tag_type>;
794+
795+
static constexpr std::size_t scaling_strlen = sizeof(scaling_tr::get_string()) - 1;
796+
static constexpr std::size_t unit_strlen = sizeof( unit_tr::get_string()) - 1;
797+
static constexpr std::size_t strlen = scaling_strlen + unit_strlen;
798+
799+
static constexpr static_string<strlen> string{ make_quantity_string<strlen>(scaling_tr::get_string()
800+
, unit_tr::get_string()) };
801+
};
802+
#pragma GCC diagnostic push
803+
#pragma GCC diagnostic ignored "-Wdeprecated"
804+
// in C++14, this is still necessary
805+
template<typename U, typename S, typename T>
806+
constexpr static_string<quantity_string_traits<U,S,T>::strlen> quantity_string_traits<U,S,T>::string;
807+
#pragma GCC diagnostic pop
808+
809+
}
810+
811+
/**
812+
* The generic version of this template obtains a compile-time string
813+
* representing the unit, scale, and tag of a quantity by referring to @sa
814+
* scale_traits and @sa unit_traits.
815+
*
816+
* Specializations of this define the strings for quantities (like "nm" for
817+
* nanometer and "kt" for kilotons). Usually, specializing @sa unit_traits
818+
* will be more convenient than specializing this, because it allows the
819+
* automatic combination with scaling prefixes. But sometimes units scale
820+
* irregularly (kilotons), and then this template can be specialized.
821+
*
822+
* @note Specializations of this must have a static_string member named
823+
* `get_string()`. (The @sa UNLIB_DEFINE_QUANTITY_LITERAL_TRAITS() and
824+
* the @sa UNLIB_DEFINE_SCALED_QUANTITY_LITERAL_TRAITS() macros are
825+
* convenient ways to create such specializations.)
826+
*
827+
* @tparam U the quantity's unit type
828+
* @tparam S the quantity's scale type
829+
* @tparam V the quantity's value type
830+
* @tparam T the quantity's tag type
831+
*/
832+
template<typename U, typename S, typename T>
833+
struct quantity_traits {
834+
using is_specialized = std::integral_constant<bool, is_scaling_string_specialized<S >::value
835+
and is_unit_string_specialized<U,T>::value>;
836+
static constexpr const auto& get_string() {return detail::quantity_string_traits<U,S,T>::string.array;}
837+
};
838+
839+
/** Find out whether quantity_traits is specialized for a specific unit, scaling, and tag */
840+
template<typename U, typename S, typename T>
841+
using is_quantity_string_specialized = typename quantity_traits<U,S,T>::is_specialized;
842+
843+
844+
/**
845+
* @{
846+
*
847+
* Get a reference to a char array or a C++ string containing the unit string
848+
* for a quantity.
849+
*
850+
* @tparam Q the quantity
851+
* @tparam U the quantity's unit type
852+
* @tparam S the quantity's scale type
853+
* @tparam V the quantity's value type
854+
* @tparam T the quantity's tag type
855+
*
856+
* @param q a quantity (its value is irrelevant)
857+
*
858+
* @return String for the unit, scale, and tag of the quantity
859+
*/
860+
template<typename U, typename S, typename V, typename T>
861+
const auto& get_quantity_c_str(const quantity<U,S,V,T>&) {return literals::quantity_traits<U,S,T>::get_string();}
862+
template<typename Q>
863+
const auto& get_quantity_c_str() {return get_quantity_c_str(Q{});}
864+
865+
template<typename U, typename S, typename V, typename T>
866+
std::string get_quantity_string(const quantity<U,S,V,T>& q) {return get_quantity_c_str(q);}
867+
template<typename Q>
868+
std::string get_quantity_string() {return get_quantity_c_str<Q>();}
869+
/** @} */
870+
871+
}
872+
873+
namespace detail {
874+
875+
template<typename U, typename S, typename V, typename T>
876+
std::ostream& stream(std::ostream& os, const quantity<U,S,V,T>& q, std::false_type)
877+
{return os << q.get();}
878+
template<typename U, typename S, typename V, typename T>
879+
std::ostream& stream(std::ostream& os, const quantity<U,S,V,T>& q, std::true_type)
880+
{
881+
return stream(os, q, std::false_type{})
882+
<< ' '
883+
<< literals::get_quantity_string(q);
884+
}
885+
886+
template<typename U, typename S, typename V, typename T>
887+
std::string to_string(const quantity<U,S,V,T>& q, std::false_type) {using std::to_string; return to_string(q.get());}
888+
template<typename U, typename S, typename V, typename T>
889+
std::string to_string(const quantity<U,S,V,T>& q, std::true_type) {
890+
return detail::to_string( q
891+
, std::false_type{})
892+
+ ' '
893+
+ literals::get_quantity_string(q);
894+
}
895+
896+
}
897+
898+
template<typename U, typename S, typename V, typename T>
899+
std::string to_string(const quantity<U,S,V,T>& q) {
900+
return detail::to_string( q
901+
, literals::is_quantity_string_specialized<U,S,T>{} );
902+
}
903+
904+
905+
/**
906+
* @brief Stream output operator
907+
*
908+
* Streams the stored value into os.
909+
*
910+
* @param os the stream
911+
* @param q the quantity
912+
* @return os
913+
*/
914+
template<typename U, typename S, typename V, typename T>
915+
std::ostream& operator<<(std::ostream& os, const quantity<U,S,V,T>& q) {
916+
return detail::stream( os
917+
, q
918+
, literals::is_quantity_string_specialized<U,S,T>{} );
919+
}
920+
921+
/**
922+
* @brief Stream input operator
923+
*
924+
* Reads from the stream into q.
925+
*
926+
* @param is the stream
927+
* @param q the quantity
928+
*
929+
* @return is
930+
*/
931+
template<typename U, typename S, typename V, typename T>
932+
std::istream& operator>>(std::istream& is, quantity<U,S,V,T>& q) {
933+
V v;
934+
if (is >> v)
935+
q = quantity<U,S,V,T>{v};
936+
return is;
937+
}
938+
741939
}
742940

743941
#endif /* UNLIB_QUANTITY_HPP */

scaling.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ template<typename T> using micro = scale_by_t< micro_scaling,T>;
108108
template<typename T> using milli = scale_by_t< milli_scaling,T>;
109109
template<typename T> using centi = scale_by_t< centi_scaling,T>;
110110
template<typename T> using deci = scale_by_t< deci_scaling,T>;
111-
template<typename T> using no_scale = T;
111+
template<typename T> using no_scale = scale_by_t< no_scaling,T>;
112112
template<typename T> using deca = scale_by_t< deca_scaling,T>;
113113
template<typename T> using hecto = scale_by_t< hecto_scaling,T>;
114114
template<typename T> using kilo = scale_by_t< kilo_scaling,T>;

0 commit comments

Comments
 (0)