nholthaus / units

a compile-time, header-only, dimensional analysis and unit conversion library built on c++14 with no dependencies.
http://nholthaus.github.io/units/
MIT License
934 stars 134 forks source link

How to print units with C++20 std::format()? #333

Closed tintor closed 2 weeks ago

tintor commented 2 weeks ago
std::format("{}", units::mass::to_string(1_kg)); // works, but not very useful in templated code
std::format("{}", units::to_string(1_kg)); // compile error
std::format("{}", to_string(1_kg)); // compile error
std::format("{}", 1_kg); // compile error

Ideally the last form would just work. Thanks!

As a workaround, any suggestions on how to create a custom formatter for units?

tintor commented 2 weeks ago

I was able to integrate it to std::format() for my use. It supports all units, data types, and allows conversion when formatting if you prefer a certain unit.

extern bool g_use_degrees_c;

template <typename U, typename T, typename CharT>
struct std::formatter<units::unit_t<U, T>, CharT> : public std::formatter<T, CharT> {
    template<class FormatContext>
    auto format(const units::unit_t<U, T>& u, FormatContext& ctx) const {
        T v = u.value();
        const char* unit = units::unit_t<U, double>(u).abbreviation();
        if constexpr (units::traits::is_temperature_unit<U>::value) {
            if (g_use_degrees_c) {
                v = units::unit_t<celsius, T>(u).value();
                unit = "C";
            }
        }
        const T va = std::abs(v);
        if (va < 1e-6) return std::format_to(std::formatter<float, CharT>::format(v * 1e9, ctx), " n{}", unit);
        if (va < 1e-3) return std::format_to(std::formatter<float, CharT>::format(v * 1e6, ctx), " u{}", unit);
        if (va < 1.0) return std::format_to(std::formatter<float, CharT>::format(v * 1e3, ctx), " m{}", unit);
        if (va < 1e3) return std::format_to(std::formatter<float, CharT>::format(v, ctx), " {}", unit);
        if (va < 1e6) return std::format_to(std::formatter<float, CharT>::format(v * 1e-3, ctx), " k{}", unit);
        return std::format_to(std::formatter<float, CharT>::format(v * 1e-6, ctx), " M{}", unit);
    }
};