5
\$\begingroup\$

I've developed a set of functions to convert a std::ratio or std::chrono::duration to a std::string or insert it into a std::ostream using operator<<. They are intended to be part of a larger library of conversion functions so I've included a few additional functions in the library for demonstration.

There are two versions of each function: the normal one in the convert namespace, and one that uses a compact form of a string in the convert::compact namespace. For example, the ratio std::milli is normally converted to the string "milli" and its compact form is "m". Not all conversions have a compact form, so in those cases the functions in convert::compact simply call their counterparts in convert.

Conversions of std::chrono::durations take advantage of the conversions for std::ratios if the period ratio matches a std::ratio typedef provided by the standard library. Similarly, proper conversions are implemented for period ratios which match a std::chrono::duration typedef provided by the standard library (e.g. std::chrono::minutes and std::chrono::hours).

Here is the header file of functions, convert.h:

/** \file
\brief Functions for the conversion of types to `std::string` or for inserting them into a `std::ostream`.
*/

#ifndef CONVERT_H
#define CONVERT_H

#include <chrono>
#include <cstdint>
#include <iostream>
#include <ratio>
#include <sstream>
#include <stdexcept>
#include <string>

/** \brief Namespace for the conversion of types to `std::string` or for inserting them into a 
`std::ostream`.
*/
namespace convert {

/** \brief Converts a type `T` to a `std::string` using `std::ostringstream`.
\throw std::invalid_argument if the type `T` cannot be converted by a `std::ostringstream`.
*/
template<typename T> inline std::string to_string(const T& t) {
    std::ostringstream oss;

    if (!(oss << t)) {
        throw std::invalid_argument("unable to convert to std::string");
    }

    return oss.str();
}

/** \brief Specialization for converting to a `std::string` when the argument is already a `std::string`,
which simply returns the argument.
*/
template<> inline std::string to_string(const std::string& s) {
    return s;
}

/** \brief Specialization for converting a `bool` to a `std::string` suggested at
https://isocpp.org/wiki/faq/templates#template-specialization-example.
Uses `std::boolalpha` to return "true" or "false" instead of "1" or "0".
*/
template<> inline std::string to_string<bool>(const bool& b) {
    std::ostringstream oss;
    oss << std::boolalpha << b;
    return oss.str();
}

/** \brief Converts a `std::ratio<Num, Denom>` to `std::string`.
The `std::string` contains a numerator and denominator reduced to their lowest
terms `num` and `den`, obtained from `std::ratio<Num, Denom>::num` and `std::ratio<Num, Denom>::den`,
respectively.
\return a `std::string` of the form ' num' if `den` is equal to 1
\return a `std::string` of the form ' num/den' otherwise
*/
template<std::intmax_t Num, std::intmax_t Denom>
inline std::string to_string(const std::ratio<Num, Denom>& r) {
    // Include a space before the numerator in case the ratio is preceded by another number
    auto str = " " + std::to_string(std::ratio<Num, Denom>::num);

    constexpr auto den = std::ratio<Num, Denom>::den;

    if (den != 1) str += "/" + std::to_string(den);

    return str;
}

/* std::yocto and std::zepto are defined in the standard if std::intmax_t can represent the
denominator. That's not the case here. Similarly, std::zetta and std::yotta are not
available. How to determine whether or not they are available?
*/

/*
template<> inline std::string to_string(const std::yocto& r) {
return "yocto";
}

template<> inline std::string to_string(const std::zepto& r) {
return "zepto";
}
*/

/** \brief Converts the ratio `std::atto` to a `std::string` consisting of the SI prefix name "atto".
*/
template<> inline std::string to_string(const std::atto& r) {
    return "atto";
}

/** \brief Converts the ratio `std::femto` to a `std::string` consisting of the SI prefix name "femto".
*/
template<> inline std::string to_string(const std::femto& r) {
    return "femto";
}

/** \brief Converts the ratio `std::pico` to a `std::string` consisting of the SI prefix name "pico".
*/
template<> inline std::string to_string(const std::pico& r) {
    return "pico";
}

/** \brief Converts the ratio `std::nano` to a `std::string` consisting of the SI prefix name "nano".
*/
template<> inline std::string to_string(const std::nano& r) {
    return "nano";
}

/** \brief Converts the ratio `std::micro` to a `std::string` consisting of the SI prefix name "micro".
*/
template<> inline std::string to_string(const std::micro& r) {
    return "micro";
}

/** \brief Converts the ratio `std::milli` to a `std::string` consisting of the SI prefix name "milli".
*/
template<> inline std::string to_string(const std::milli& r) {
    return "milli";
}

/** \brief Converts the ratio `std::centi` to a `std::string` consisting of the SI prefix name "centi".
*/
template<> inline std::string to_string(const std::centi& r) {
    return "centi";
}

/** \brief Converts the ratio `std::deci` to a `std::string` consisting of the SI prefix name "deci".
*/
template<> inline std::string to_string(const std::deci& r) {
    return "deci";
}

/** \brief Converts the ratio `std::deca` to a `std::string` consisting of the SI prefix name "deca".
*/
template<> inline std::string to_string(const std::deca& r) {
    return "deca";
}

/** \brief Converts the ratio `std::hecto` to a `std::string` consisting of the SI prefix name "hecto".
*/
template<> inline std::string to_string(const std::hecto& r) {
    return "hecto";
}

/** \brief Converts the ratio `std::kilo` to a `std::string` consisting of the SI prefix name "kilo".
*/
template<> inline std::string to_string(const std::kilo& r) {
    return "kilo";
}

/** \brief Converts the ratio `std::mega` to a `std::string` consisting of the SI prefix name "mega".
*/
template<> inline std::string to_string(const std::mega& r) {
    return "mega";
}

/** \brief Converts the ratio `std::giga` to a `std::string` consisting of the SI prefix name "giga".
*/
template<> inline std::string to_string(const std::giga& r) {
    return "giga";
}

/** \brief Converts the ratio `std::tera` to a `std::string` consisting of the SI prefix name "tera".
*/
template<> inline std::string to_string(const std::tera& r) {
    return "tera";
}

/** \brief Converts the ratio `std::peta` to a `std::string` consisting of the SI prefix name "peta".
*/
template<> inline std::string to_string(const std::peta& r) {
    return "peta";
}

/** \brief Converts the ratio `std::exa` to a `std::string` consisting of the SI prefix name "exa".
*/
template<> inline std::string to_string(const std::exa& r) {
    return "exa";
}

/*
template<> inline std::string to_string(const std::zetta& r) {
return "zetta";
}

template<> inline std::string to_string(const std::yotta& r) {
return "yotta";
}
*/

/** \brief Inserts a `std::ratio<Num, Denom>` into a `std::ostream`.

The argument is converted to a `std::string` using to_string(const std::ratio<Num, Denom>&), which is
inserted into the `std::ostream`.
*/
template<std::intmax_t Num, std::intmax_t Denom>
std::ostream& operator<<(std::ostream& os, const std::ratio<Num, Denom>& r) {
    return os << convert::to_string(r);
}

/** \brief Returns a `std::string` representing the base time unit for a `std::chrono::duration`
with period type `std::ratio<num, den>`.

This is a function template so users may specialize it for a specific `std::ratio`.
\return "second", the SI base time unit
*/
template<typename std::intmax_t num, typename std::intmax_t den>
inline std::string duration_unit() {
    return "second";
}

/** \brief Specialization of the `std::chrono::duration` base time unit `std::string` for the type
`std::chrono::minutes`.
\return "minute"
*/
template<> inline std::string
duration_unit<std::chrono::minutes::period::num, std::chrono::minutes::period::den>() {
    return "minute";
}

/** \brief Specialization of the `std::chrono::duration` base time unit `std::string` for the type
`std::chrono::hours`.
\return "hour"
*/
template<> inline std::string
duration_unit<std::chrono::hours::period::num, std::chrono::hours::period::den>() {
    return "hour";
}

/** \brief Converts a `std::chrono::duration` to a `std::string`.
\tparam Rep an arithmetic type representing the number of ticks for a `std::chrono::duration`.
\tparam Num the numerator of the `std::ratio` used as the period type of the `std::chrono::duration`.
\tparam Denom the denominator of the `std::ratio` used as the period type of the `std::chrono::duration`.
\return a `std::string` of the form "n [SI prefix][base unit]". `n` is the number of ticks in the
`std::chrono::duration` converted to a `std::string`. [base unit] is a base time unit (usually the SI
second). [SI prefix] is an SI prefix (such as "milli") which is present if the base unit is seconds
and the argument's period type is an SI ratio. The base unit is in its plural form (e.g. "seconds")
unless the `std::chrono::duration` has exactly 1 tick, in which case the base unit is singular.
*/
template<typename Rep, std::intmax_t Num, std::intmax_t Denom>
inline std::string to_string(const std::chrono::duration<Rep, std::ratio<Num, Denom> >& d) {
    auto ticks = d.count();
    auto str = std::to_string(ticks) + " ";

    // Determine the reduced Num/Denom ratio
    constexpr std::intmax_t num = std::ratio<Num, Denom>::num;
    constexpr std::intmax_t den = std::ratio<Num, Denom>::den;

    // Determine the base time unit (usually seconds, but may be minutes, hours, etc.)
    auto unit_str = duration_unit<num, den>();

    // Add the ratio prefix (e.g. "milli") if the base unit is seconds and the ratio is not 1
    if (unit_str == "second" && !(num == 1 && den == 1)) {
        str += to_string(std::ratio<num, den>());
    }

    str += unit_str;

    // Pluralize if the number of ticks is not equal to exactly 1
    if (ticks != 1) str += "s";

    return str;
}

/** \brief Inserts a `std::chrono::duration` into a `std::ostream`.

The argument is converted to a `std::string` using
to_string(const std::chrono::duration<Rep, std::ratio<Num, Denom> >&),
which is inserted into the `std::ostream`.
*/
template<typename Rep, std::intmax_t Num, std::intmax_t Denom>
std::ostream& operator<<(std::ostream& os,
    const std::chrono::duration<Rep, std::ratio<Num, Denom> >& d) {
    return os << convert::to_string<Rep, Num, Denom>(d);
}

/** \brief Namespace for the conversion of types to `std::string` or for inserting them into a
`std::ostream` using a compact form.
*/
namespace compact {

/** \brief Converts a type `T` to a `std::string` using `std::ostringstream`.

In general there is no compact `std::string` form of a generic type `T` so this function simply calls
convert::to_string(const T&). This is included in the compact namespace so that users can call
`convert::compact::to_string()` on any type for which a call to `convert::to_string()` is permitted.
\throw std::invalid_argument if the type `T` cannot be converted by a `std::ostringstream`.
*/
template<typename T> inline std::string to_string(const T& t) {
    return convert::to_string(t);
}

/** \brief Specialization for converting a `bool` to a `std::string`.

Uses `std::noboolalpha` to return "1" or "0".
*/
template<> inline std::string to_string<bool>(const bool& b) {
    std::ostringstream oss;
    oss << std::noboolalpha << b;
    return oss.str();
}

/** \brief Converts a `std::ratio<Num, Denom>` to `std::string`.

The compact form is the same as the normal conversion by convert::to_string(const std::ratio<Num, Denom>&).
This is included in the compact namespace so that users can call `convert::compact::to_string()` on
any `std::ratio` and convert it properly.
*/
template<std::intmax_t Num, std::intmax_t Denom>
inline std::string to_string(const std::ratio<Num, Denom>& r) {
    return convert::to_string(r);
}

/** \brief Inserts a `std::ratio<Num, Denom>` into a `std::ostream`.

The argument is converted to a `std::string` using to_string(const std::ratio<Num, Denom>&), which is
inserted into the `std::ostream`.
*/
template<std::intmax_t Num, std::intmax_t Denom>
std::ostream& operator<<(std::ostream& os, const std::ratio<Num, Denom>& r) {
    return os << convert::compact::to_string(r);
}

/** \brief Returns a `std::string` representing the compact symbol form of the base time unit for a
`std::chrono::duration` with period type `std::ratio<num, den>`.

This is a function template so users may specialize it for a specific `std::ratio`.
\return "s", the SI base time unit symbol
*/
template<typename std::intmax_t num, typename std::intmax_t den>
inline std::string duration_unit() {
    return "s";
}

/** \brief Specialization of the compact `std::string` form of the `std::chrono::duration` base time
unit for the type `std::chrono::minutes`.
\return "min"
*/
template<> inline std::string
duration_unit<std::chrono::minutes::period::num, std::chrono::minutes::period::den>() {
    return "min";
}

/** \brief Specialization of the compact `std::string` form of the `std::chrono::duration` base time
unit for the type `std::chrono::hours`.
\return "h"
*/
template<> inline std::string
duration_unit<std::chrono::hours::period::num, std::chrono::hours::period::den>() {
    return "h";
}

/** \brief Converts a `std::chrono::duration` to a `std::string` in compact form.

This is the compact form of convert::to_string(const std::chrono::duration<Rep, std::ratio<Num, Denom> >&).
\return a `std::string` of the form "n [SI prefix][base unit]". `n` is the number of ticks in the
`std::chrono::duration` converted to a `std::string`. [base unit] is a base time unit in compact
form (usually "s", the symbol of the SI second). [SI prefix symbol] is an SI prefix (such as "m" for
"milli") which is present if the base unit is seconds and the argument's period type is an SI ratio.
*/
template<typename Rep, std::intmax_t Num, std::intmax_t Denom>
inline std::string to_string(const std::chrono::duration<Rep, std::ratio<Num, Denom> >& d) {
    auto str = std::to_string(d.count());

    // Determine the reduced Num/Denom ratio
    constexpr std::intmax_t num = std::ratio<Num, Denom>::num;
    constexpr std::intmax_t den = std::ratio<Num, Denom>::den;

    // Determine the base time unit (usually seconds, but may be minutes, hours, etc.)
    auto unit_str = duration_unit<num, den>();

    // Add the ratio prefix (e.g. "m") if the base unit is seconds and the ratio is not 1
    if (unit_str == "s" && !(num == 1 && den == 1)) {
        str += to_string(std::ratio<num, den>());
    }

    str += unit_str;

    return str;
}

/*
template<> inline std::string to_string(const std::yocto& r) {
    return "y";
}

template<> inline std::string to_string(const std::zepto& r) {
    return "z";
}*/

/** \brief Converts the ratio `std::atto` to a `std::string` consisting of the symbol for the SI
prefix "atto".
\return "a"
*/
template<> inline std::string to_string(const std::atto& r) {
    return "a";
}

/** \brief Converts the ratio `std::femto` to a `std::string` consisting of the symbol for the SI
prefix "femto".
\return "f"
*/
template<> inline std::string to_string(const std::femto& r) {
    return "f";
}

/** \brief Converts the ratio `std::pico` to a `std::string` consisting of the symbol for the SI
prefix "pico".
\return "p"
*/
template<> inline std::string to_string(const std::pico& r) {
    return "p";
}

/** \brief Converts the ratio `std::nano` to a `std::string` consisting of the symbol for the SI
prefix "nano".
\return "n"
*/
template<> inline std::string to_string(const std::nano& r) {
    return "n";
}

/** \brief Converts the ratio `std::micro` to a `std::string` consisting of a representation of the
symbol for the SI prefix "micro".
\return "u", a substitute representation of the SI symbol for micro, which is based on the Greek letter
mu.
*/
template<> inline std::string to_string(const std::micro& r) {
    return "u";
}

/** \brief Converts the ratio `std::milli` to a `std::string` consisting of the symbol for the SI
prefix "milli".
\return "m"
*/
template<> inline std::string to_string(const std::milli& r) {
    return "m";
}

/** \brief Converts the ratio `std::centi` to a `std::string` consisting of the symbol for the SI
prefix "centi".
\return "c"
*/
template<> inline std::string to_string(const std::centi& r) {
    return "c";
}

/** \brief Converts the ratio `std::deci` to a `std::string` consisting of the symbol for the SI
prefix "deci".
\return "d"
*/
template<> inline std::string to_string(const std::deci& r) {
    return "d";
}

/** \brief Converts the ratio `std::deca` to a `std::string` consisting of the symbol for the SI
prefix "deca".
\return "da"
*/
template<> inline std::string to_string(const std::deca& r) {
    return "da";
}

/** \brief Converts the ratio `std::hecto` to a `std::string` consisting of the symbol for the SI
prefix "hecto".
\return "h"
*/
template<> inline std::string to_string(const std::hecto& r) {
    return "h";
}

/** \brief Converts the ratio `std::kilo` to a `std::string` consisting of the symbol for the SI
prefix "kilo".
\return "k"
*/
template<> inline std::string to_string(const std::kilo& r) {
    return "k";
}

/** \brief Converts the ratio `std::mega` to a `std::string` consisting of the symbol for the SI
prefix "mega".
\return "M"
*/
template<> inline std::string to_string(const std::mega& r) {
    return "M";
}

/** \brief Converts the ratio `std::giga` to a `std::string` consisting of the symbol for the SI
prefix "giga".
\return "G"
*/
template<> inline std::string to_string(const std::giga& r) {
    return "G";
}

/** \brief Converts the ratio `std::tera` to a `std::string` consisting of the symbol for the SI
prefix "tera".
\return "T"
*/
template<> inline std::string to_string(const std::tera& r) {
    return "T";
}

/** \brief Converts the ratio `std::peta` to a `std::string` consisting of the symbol for the SI
prefix "peta".
\return "P"
*/
template<> inline std::string to_string(const std::peta& r) {
    return "P";
}

/** \brief Converts the ratio `std::exa` to a `std::string` consisting of the symbol for the SI
prefix "exa".
\return "E"
*/
template<> inline std::string to_string(const std::exa& r) {
    return "E";
}

/*
template<> inline std::string to_string(const std::zetta& r) {
return "Z";
}

template<> inline std::string to_string(const std::yotta& r) {
return "Y";
}*/

/** \brief Inserts a `std::chrono::duration` in compact form into a `std::ostream`.

The argument is converted to a `std::string` using
to_string(const std::chrono::duration<Rep, std::ratio<Num, Denom> >&),
which is inserted into the `std::ostream`.
*/
template<typename Rep, std::intmax_t Num, std::intmax_t Denom>
std::ostream& operator<<(std::ostream& os, const std::chrono::duration<Rep, std::ratio<Num, Denom> >& d) {
    return os << convert::compact::to_string<Rep, Num, Denom>(d);
}

} // end namespace compact
} // end namespace convert

#endif

For the std::chrono::duration conversions, duration_unit() is intended to be specialized by users if desired for specific time periods.

Here is a little demo program demonstrating the conversions and a specialization of duration_unit() for a time period corresponding to a day:

#include <iostream>
#include <fstream>
#include <string>

#include "convert.h"

using namespace std::chrono_literals;

template<typename T>
void print(std::ostream& os, const T& t) {
    os << '\'' << convert::to_string(t) << "\' or \'"
        << convert::compact::to_string(t) << "\' using to_string(),\t";

    {
        using convert::operator<<;
        os << '\'' << t << "\' or \'";
    }
    {
        using convert::compact::operator<<;
        os << t << "\' using operator<<\n";
    }
}

// User-defined duration unit strings
namespace convert {

template<> inline std::string
duration_unit<std::chrono::hours::period::num * 24, std::chrono::hours::period::den>() {
    return "day";
}

namespace compact {

template<> inline std::string
duration_unit<std::chrono::hours::period::num * 24, std::chrono::hours::period::den>() {
    return "d";
}

} // end namespace compact
} // end namespace convert

int main() {
    std::ofstream ofs("demo.txt");
    std::ostream& os = ofs; // or std::cout

    os << "SI std::ratio conversions:\n";
    print(os, std::atto());
    print(os, std::femto());
    print(os, std::pico());
    print(os, std::nano());
    print(os, std::micro());
    print(os, std::milli());
    print(os, std::centi());
    print(os, std::deci());
    print(os, std::deca());
    print(os, std::hecto());
    print(os, std::kilo());
    print(os, std::mega());
    print(os, std::giga());
    print(os, std::tera());
    print(os, std::peta());
    print(os, std::exa());
    os << '\n';

    // Some non-SI ratios to test
    auto r1 = std::ratio<60>();
    auto r2 = std::ratio<1, 60>();
    auto r3 = std::ratio<2, 120>(); // not in reduced form
    auto r4 = std::ratio<1>();

    os << "Other std::ratio conversions:\n";
    print(os, r1);
    print(os, r2);
    print(os, r3);
    print(os, r4);
    os << '\n';

    // Some durations to test
    auto d1 = 4ns;
    auto d2 = 1us;
    auto d3 = 1.5ms;
    auto d4 = -9.1s;
    std::chrono::duration<int, std::ratio<120, 2> > d5(50); // period is equal to minutes
    std::chrono::hours d6(2);
    std::chrono::duration<std::intmax_t, std::exa> d7(7);
    std::chrono::duration<double, std::ratio<24 * 60 * 60, 1> > d8(5.5); // days

    os << "std::chrono::duration conversions:\n";
    print(os, d1);
    print(os, d2);
    print(os, d3);
    print(os, d4);
    print(os, d5);
    print(os, d6);
    print(os, d7);
    print(os, d8);
    os << '\n';

    return 0;
}

The output of the demo program is:

SI std::ratio conversions:
'atto' or 'a' using to_string(),    'atto' or 'a' using operator<<
'femto' or 'f' using to_string(),   'femto' or 'f' using operator<<
'pico' or 'p' using to_string(),    'pico' or 'p' using operator<<
'nano' or 'n' using to_string(),    'nano' or 'n' using operator<<
'micro' or 'u' using to_string(),   'micro' or 'u' using operator<<
'milli' or 'm' using to_string(),   'milli' or 'm' using operator<<
'centi' or 'c' using to_string(),   'centi' or 'c' using operator<<
'deci' or 'd' using to_string(),    'deci' or 'd' using operator<<
'deca' or 'da' using to_string(),   'deca' or 'da' using operator<<
'hecto' or 'h' using to_string(),   'hecto' or 'h' using operator<<
'kilo' or 'k' using to_string(),    'kilo' or 'k' using operator<<
'mega' or 'M' using to_string(),    'mega' or 'M' using operator<<
'giga' or 'G' using to_string(),    'giga' or 'G' using operator<<
'tera' or 'T' using to_string(),    'tera' or 'T' using operator<<
'peta' or 'P' using to_string(),    'peta' or 'P' using operator<<
'exa' or 'E' using to_string(), 'exa' or 'E' using operator<<

Other std::ratio conversions:
' 60' or ' 60' using to_string(),   ' 60' or ' 60' using operator<<
' 1/60' or ' 1/60' using to_string(),   ' 1/60' or ' 1/60' using operator<<
' 1/60' or ' 1/60' using to_string(),   ' 1/60' or ' 1/60' using operator<<
' 1' or ' 1' using to_string(), ' 1' or ' 1' using operator<<

std::chrono::duration conversions:
'4 nanoseconds' or '4ns' using to_string(), '4 nanoseconds' or '4ns' using operator<<
'1 microsecond' or '1us' using to_string(), '1 microsecond' or '1us' using operator<<
'1.500000 milliseconds' or '1.500000ms' using to_string(),  '1.500000 milliseconds' or '1.500000ms' using operator<<
'-9.100000 seconds' or '-9.100000s' using to_string(),  '-9.100000 seconds' or '-9.100000s' using operator<<
'50 minutes' or '50min' using to_string(),  '50 minutes' or '50min' using operator<<
'2 hours' or '2h' using to_string(),    '2 hours' or '2h' using operator<<
'7 exaseconds' or '7Es' using to_string(),  '7 exaseconds' or '7Es' using operator<<
'5.500000 days' or '5.500000d' using to_string(),   '5.500000 days' or '5.500000d' using operator<<

I'm looking for suggestions to improve any aspect of the code. In particular, I'm looking for suggestions to improve my use of C++11 and C++14 (I'm compiling with Visual Studio 2017 but most of my C++ experience has been with a C++03 compiler). Also, some specific questions:

  1. std::yocto, std::zepto, std::zetta, and std::yotta are only defined if std::intmax_t can represent the ratio, and indeed on my system they are not defined. Is there a way for me to detect when those types are defined on the system so that I can provide conversions for those types yet avoid compiler errors when they are not defined?
  2. Eventually I plan to expand this library to support additional std::basic_string types, not just std::string. Are there any issues with this design which will cause problems when I add this support?
  3. The SI symbol for "micro" is really "µ" but I'm using "u" for simplicity (and since the character type of std::string is char). Since I plan to expand this library for wider character types, is it possible to determine if "µ" can be represented properly for a given character type?
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

The numerous compilation warnings for unused variable r in the various to_string() functions can be eliminated by simply not naming the variable.

Most of the to_string() don't need to be templates. Simple overloads work fine. And for those that do need to be templates, inline is implied.

So we end up with functions such as

inline std::string to_string(const std::deca&) {
    return "deca";
}

If we were not constrained to support C++11, we could make these functions constexpr.


This logic happens to work for the English words we use, but is going to prove difficult to internationalise:

// Pluralize if the number of ticks is not equal to exactly 1
if (ticks != 1) str += "s";

The test program rather rudely writes a file by default. Luckily it didn't overwrite anything I needed, but please don't do that in future. Either accept a file name as argument or (preferably) just write to standard output and allow the user to redirect as desired.


We're missing tests of boolean conversion:

    os << "Booolean conversions:\n";
    print(os, false);
    print(os, true);
    os << '\n';

This gives us

Booolean conversions:
'false' or '0' using to_string(),   '0' or '0' using operator<<
'true' or '1' using to_string(),    '1' or '1' using operator<<

It seems we're missing a << overload for the long form of these. That's not as easy to resolve as you might think - my attempts to do so all resulted in ambiguity with the one defined in std.


We could use the preprocessor macro INTMAX_MAX (defined by <cstdint> which we're already including) to determine whether the larger ratios are available, and support them if so:

#if INT_MAX / 1000000000000000000 >= 1000
/* ratios smaller than femto are defined in the standard if std::intmax_t
   can represent the denominator. Similarly, ratios larger than exa are
   only available it it can represent the numerator.
*/
inline std::string to_string(const std::zepto&) {
return "zepto";
}
inline std::string to_string(const std::zetta&) {
return "zetta";
}

#if INT_MAX / 1000000000000000000 >= 1000000
inline std::string to_string(const std::yocto&) {
return "yocto";
}
inline std::string to_string(const std::yotta&) {
return "yotta";
}

#if __cplusplus >= 202600L
/* ratios larger than yocto are only defined from C++26 onwards */

#if INT_MAX / 1000000000000000000 >= 1000000000
inline std::string to_string(const std::ronto&) {
return "ronto";
}
inline std::string to_string(const std::ronna&) {
return "ronna";
}

#if INT_MAX / 1000000000000000000 >= 1000000000000
inline std::string to_string(const std::quecto&) {
return "quecto";
}
inline std::string to_string(const std::quetta&) {
return "quetta";
}

#endif
#endif
#endif
#endif
#endif
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.