#include <cstddef> // std::size_t
#include <string> // std::string
#include <charconv> // std::to_chars
#include <cmath> // std::ceil
#include <iterator>
#include <type_traits>
#include <cassert>
#if __cplusplus < 201703L
#error At least C++17 is required
#endif
namespace format {
template<typename, typename = void>
struct FormatHelper;
namespace validator {
template<typename T, size_t = sizeof(FormatHelper<T>)>
constexpr bool has_format_helper(std::nullptr_t) {
return true;
}
template<typename T>
constexpr bool has_format_helper(...) {
return false;
}
template<typename T>
constexpr bool has_format_helper_v = has_format_helper<T>(nullptr);
template<typename T, typename = std::enable_if_t<
std::is_same_v<std::string,
decltype(std::declval<const T&>().format())>>>
constexpr bool has_format(std::nullptr_t) {
return true;
}
template<typename T>
constexpr bool has_format(...) {
return false;
}
template<typename T>
constexpr bool has_format_v = has_format<T>(nullptr);
}
template<>
struct FormatHelper<bool> {
explicit FormatHelper(bool b) : val(b) {}
std::size_t estimate_size() const {
return val ? 4 : 5;
}
void append_to(std::string& s) const {
s += val ? "true" : "false";
}
private:
bool val;
};
template<typename T>
struct FormatHelper<std::vector<T>, std::enable_if_t<validator::has_format_helper_v<T>>> {
explicit FormatHelper(const std::vector<T>& val) : vec(val) {
fmt.reserve(val.size());
for (const auto& v : val) {
fmt.emplace_back(v);
}
}
std::size_t estimate_size() const {
std::size_t s = 2; // "{}"
for (std::size_t i = 0; i < vec.size(); i++) {
s += fmt[i].estimate_size();
if (i != vec.size() - 1) {
s += 2; // ", "
}
}
return s;
}
void append_to(std::string& s) const {
s += "{";
for (std::size_t i = 0; i < vec.size(); i++) {
fmt[i].append_to(s);
if (i != vec.size() - 1) {
s += ", ";
}
}
s += "}";
}
private:
const std::vector<T>& vec;
std::vector<FormatHelper<T>> fmt;
};
template<typename T>
struct FormatHelper<T, std::enable_if_t<std::is_integral_v<T>>> {
constexpr static int BASE = 10;
// +1 for potential '-' and +1 because digits10 will be rounded down
constexpr static size_t BUF_SIZE = 2 + std::numeric_limits<T>::digits10;
explicit FormatHelper(const T& val) : val(val) {}
std::size_t estimate_size() const {
return BUF_SIZE;
}
void append_to(std::string& s) const {
char buf[BUF_SIZE];
auto [end, ec] = std::to_chars(std::begin(buf), std::end(buf), val, BASE);
assert(ec == std::errc());
s += std::string_view(buf, end - buf);
}
private:
const T& val;
};
template<typename T>
struct FormatHelper<T, std::enable_if_t<validator::has_format_v<T>>> {
constexpr static std::size_t DEFAULT_SIZE = 10;
explicit FormatHelper(const T& val) : val(val) {}
std::size_t estimate_size() const {
return DEFAULT_SIZE;
}
void append_to(std::string& s) const {
s += val.format();
}
private:
const T& val;
};
template<typename T>
std::enable_if_t<validator::has_format_helper_v<T>, std::string> make_string(const T& value) {
FormatHelper<T> helper(value); // Explicit!
std::string formatted;
formatted.reserve(helper.estimate_size());
helper.append_to(formatted);
formatted.shrink_to_fit();
return formatted;
}
} // namespace format
Comments