diff options
Diffstat (limited to 'cxxformat.hpp')
| -rw-r--r-- | cxxformat.hpp | 656 |
1 files changed, 0 insertions, 656 deletions
diff --git a/cxxformat.hpp b/cxxformat.hpp deleted file mode 100644 index 47c0cd7..0000000 --- a/cxxformat.hpp +++ /dev/null @@ -1,656 +0,0 @@ -/* -Copyright 2019 Mittendrein Markus - -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 <string> -#include <string_view> -#include <optional> -#include <cstring> -#include <stdexcept> -#include <cstdint> -#include <concepts> - -namespace format { - -using FormatException = std::invalid_argument; - -template<typename To, typename From, typename Enable = void> -struct FormatConvert {}; - -template<typename To, typename From> -struct ImplicitConversion -{ - static constexpr To convert(From from) - { - return from; - } -}; - -template<std::signed_integral From> -struct FormatConvert<std::intmax_t, From> : ImplicitConversion<std::intmax_t, From> {}; - -template<std::unsigned_integral From> -struct FormatConvert<std::intmax_t, From, std::enable_if_t<(sizeof(From) < sizeof(std::intmax_t))>> : ImplicitConversion<std::intmax_t, From> {}; - -template<std::unsigned_integral From> -struct FormatConvert<std::uintmax_t, From> : ImplicitConversion<std::uintmax_t, From> {}; - -template<std::convertible_to<std::string_view> String> -struct FormatConvert<std::string_view, String> : ImplicitConversion<std::string_view, const String &> {}; - -template<std::floating_point From> -struct FormatConvert<long double, From> : ImplicitConversion<long double, const From &> {}; - -template<typename Pointer> -struct FormatConvert<const void *, Pointer, std::enable_if_t<std::is_pointer_v<Pointer> && !std::is_array_v<std::remove_pointer_t<Pointer>>>> : ImplicitConversion<const void *, const Pointer> {}; - -template<typename Pointer> -struct FormatConvert<const void *, Pointer, std::enable_if_t<std::is_pointer_v<Pointer> && std::is_array_v<std::remove_pointer_t<Pointer>>>> : ImplicitConversion<const void*, const Pointer> {}; - -template<typename From, typename Enable = void> -struct AutoConversion -{ - static bool conversion() - { - throw FormatException{""}; - } -}; - -template<char Conversion, typename From> -struct SimpleAutoConversion -{ - static constexpr char conversion() - { - return Conversion; - } -}; - -template<std::unsigned_integral From> -struct AutoConversion<From> : SimpleAutoConversion<'u', From> {}; - -template<std::signed_integral From> -struct AutoConversion<From> : SimpleAutoConversion<'d', From> {}; - -template<std::floating_point From> -struct AutoConversion<From> : SimpleAutoConversion<'G', From> {}; - -template<std::convertible_to<std::string_view> From> -struct AutoConversion<From> : SimpleAutoConversion<'s', From> {}; - -template<> -struct AutoConversion<char> : SimpleAutoConversion<'c', const char> {}; - -namespace detail -{ - template<typename T, bool Ok = false> - struct StointHelper - { - static T convert(const std::string &, std::size_t *, int) - { - static_assert(Ok, "stoint: Unsupported type"); - return T{}; - } - }; - - template<typename T, T(converter)(const std::string &, std::size_t *, int)> - struct StointHelperConverter - { - static T convert(const std::string &str, std::size_t *pos, int base) - { - return converter(str, pos, base); - } - }; - - template<> - struct StointHelper<int> : StointHelperConverter<int, std::stoi> {}; - template<> - struct StointHelper<long> : StointHelperConverter<long, std::stol> {}; - template<> - struct StointHelper<long long> : StointHelperConverter<long long, std::stoll> {}; - template<> - struct StointHelper<unsigned long> : StointHelperConverter<unsigned long, std::stoul> {}; - template<> - struct StointHelper<unsigned long long> : StointHelperConverter<unsigned long long, std::stoull> {}; - - template<typename T> - T stoint(const std::string &str, std::size_t *pos = nullptr, int base = 10) - { - return StointHelper<T>::convert(str, pos, base); - } - - template<typename... Args> - std::string strprintf(const char *fmt, Args... args) - { - int size = std::snprintf(nullptr, 0, fmt, args...) + 1; - std::string result; - result.resize(size); - std::snprintf(result.data(), size, fmt, args...); - result.resize(size - 1); - return result; - } - - template<typename To, typename From, bool allowFallback, typename Enable = void> - struct FormatConvertHelper - { - static constexpr To convert(const From &) - { - static_assert(allowFallback, "Impossible conversion needed"); - throw FormatException{""}; - } - }; - - template<typename T, bool allowFallback> - struct FormatConvertHelper<T, T, allowFallback> - { - static constexpr const T &convert(const T &from) - { - return from; - } - }; - - template<typename To, typename From, bool allowFallback> - struct FormatConvertHelper<To, From, allowFallback, std::enable_if_t<!std::is_same_v<To, From> && std::is_same_v<std::remove_cvref_t<decltype(FormatConvert<To, std::remove_cvref_t<From>>::convert(std::declval<From>()))>, std::remove_cvref_t<To>>>> - { - static constexpr To convert(const From &from) - { - return FormatConvert<To, std::remove_cvref_t<From>>::convert(from); - } - }; - - template<typename To, bool allowFallback = true, typename From> - auto convert(const From &from, const char *toName, std::size_t index = 0) - { - try - { - return FormatConvertHelper<To, From, allowFallback>::convert(from); - } - catch (const FormatException &) - { - throw FormatException{"Argument#" + std::to_string(index) + " is not convertible to " + std::string{toName}}; - } - } - - template<typename From, typename Enable = void> - struct StringOrStringViewHelper - { - using type = std::string_view; - }; - - template<typename From> - struct StringOrStringViewHelper<From, std::enable_if_t<std::is_same_v<std::string, std::decay_t<decltype(FormatConvert<std::string, From>::convert(std::declval<From>()))>>>> - { - using type = std::string; - }; - - template<typename From> - using string_or_string_view = typename StringOrStringViewHelper<From>::type; - - template<bool allowPointer = false, bool allowFallback = true, typename From> - constexpr char autoConversionSpecifier(std::size_t index = 0) - { - try - { - if constexpr (!allowFallback) - { - if constexpr(!std::is_same_v<char, decltype(AutoConversion<std::remove_cvref_t<From>>::conversion())>) - { - if constexpr (std::is_pointer_v<From>) - { - if constexpr (allowPointer) - { - return 'p'; - } - else - { - if constexpr (!allowFallback) static_assert(allowPointer, "%v doesn't allow automatic conversions for pointers; use %V to alsow allow arbitrary pointers"); - else throw FormatException{"Argument#" + std::to_string(index) + " is not applicable for auto conversion with %v, use %V to also allow arbitrary pointers"}; - } - } - } - } - return AutoConversion<std::remove_cvref_t<From>>::conversion(); - } - catch (const FormatException &) - { - if constexpr (std::is_pointer_v<From>) - { - if constexpr (allowPointer) - { - return 'p'; - } - else - { - if constexpr (!allowFallback) static_assert(allowPointer, "%v doesn't allow automatic conversions for pointers; use %V to alsow allow arbitrary pointers"); - throw FormatException{"Argument#" + std::to_string(index) + " is not applicable for auto conversion with %v, use %V to also allow arbitrary pointers"}; - } - } - throw FormatException{"Argument#" + std::to_string(index) + " is not applicable for auto conversion (%v / %V)"}; - } - } - - std::string format(std::string_view fmt, const std::size_t argumentIndex) - { - const auto fmtSize = fmt.size(); - for (std::size_t i = 0; i < fmtSize; ++i) - { - if (fmt[i] == '%') - { - if (i >= fmtSize - 1) - { - throw FormatException{"Incomplete directive"}; - } - - if (fmt[++i] == '%') - { - return std::string{fmt.substr(0, i)} + format(fmt.substr(i + 1), argumentIndex); - } - - throw FormatException{"Not enough arguments passed"}; - } - } - return std::string{fmt}; - } - - template<typename Arg, typename... Args> - std::string format(std::string_view fmt, const std::size_t argumentIndex, Arg &&arg, Args &&... args) - { - const auto fmtSize = fmt.size(); - for (std::size_t i = 0; i < fmtSize; ++i) - { - auto checkFmtLength = [i, fmtSize] - { - if (i >= fmtSize - 1) - { - throw FormatException{"Incomplete directive"}; - } - }; - const auto current = fmt[i]; - if (current == '%') - { - const auto directiveStart = i; - checkFmtLength(); - std::string specification{"%"}; - - if (fmt[i + 1] == '%') // handle %% here - { - return std::string{fmt.substr(0, directiveStart + 1)} + format(fmt.substr(i + 1), argumentIndex, std::forward<Arg>(arg), std::forward<Args>(args)...); - } - while(i < fmtSize - 1) - { - switch (const auto flag = fmt[++i]) - { - case '#': case '0': case '-': case ' ': case '+': - specification.push_back(flag); - checkFmtLength(); - break; - default: - --i; - goto afterFlagLoop; - } - } - afterFlagLoop: - - std::string tempNumber; - // field width - while (std::isdigit(fmt[++i])) - { - tempNumber.push_back(fmt[i]); - checkFmtLength(); - } - --i; - const auto fieldWidth = tempNumber.empty() ? 0 : stoint<std::size_t>(tempNumber); - tempNumber.clear(); - - std::optional<std::size_t> precision; - // precision - if (fmt[++i] == '.') - { - checkFmtLength(); - while (std::isdigit(fmt[++i])) - { - tempNumber.push_back(fmt[i]); - checkFmtLength(); - } - precision = tempNumber.empty() ? 0 : stoint<std::size_t>(tempNumber); - } - --i; - - std::string conversion{fmt[++i]}; - if(conversion.front() == 'v') - { - conversion = autoConversionSpecifier<false, true, Arg>(argumentIndex); - } - else if(conversion.front() == 'V') - { - conversion = autoConversionSpecifier<true, true, Arg>(argumentIndex); - } - - auto continueFormatting = [directiveStart, &args..., fmt, &specification, &fieldWidth, &precision, i, conversion, argumentIndex](auto length, auto converted) - { - if (fieldWidth) specification += std::to_string(fieldWidth); - if (precision) specification += '.' + std::to_string(*precision); - if (length) specification += length; - specification += conversion; - return std::string{fmt.substr(0, directiveStart)} + strprintf(specification.c_str(), converted) + format(fmt.substr(i + 1), argumentIndex + 1, std::forward<Args>(args)...); - }; - - switch (conversion.back()) - { - case 'c': - return continueFormatting(false, convert<char>(std::forward<Arg>(arg), "character", argumentIndex)); - - case 'd': case 'i': - return continueFormatting('j', convert<std::intmax_t>(std::forward<Arg>(arg), "signed integer", argumentIndex)); - - case 'u': case 'o': case 'x': case 'X': - return continueFormatting('j', convert<std::uintmax_t>(std::forward<Arg>(arg), "unsigned integer", argumentIndex)); - - case 's': - { - const auto& string = convert<string_or_string_view<std::remove_cvref_t<Arg>>>(std::forward<Arg>(arg), "string", argumentIndex); - precision = precision ? std::min(*precision, string.size()) : string.size(); - return continueFormatting(false, string.data()); - } - - case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': case 'a': case 'A': - return continueFormatting(false, convert<double>(std::forward<Arg>(arg), "double", argumentIndex)); - - case 'p': - return continueFormatting(false, convert<const void *>(std::forward<Arg>(arg), "pointer", argumentIndex)); - - default: - throw FormatException{std::string{"Unknown conversion specifier: \""} + conversion + "\""}; - } - } - } - - throw FormatException{std::to_string(argumentIndex - 1) + " placeholders found, but " + std::to_string(argumentIndex + sizeof...(args)) + " arguments passed"}; - } -} - -template<bool NoThrow = false, typename String, typename... Args> -std::string format(const String &fmt, Args &&... args) -{ - const auto fmtStr = detail::convert<std::string_view, false>(fmt, "string"); - try - { - return detail::format(fmtStr, 1, std::forward<Args>(args)...); - } - catch (const FormatException &e) - { - const auto &message = std::string{"format(\""} + std::string{fmtStr} + "\", " + std::to_string(sizeof...(args)) + " more arguments...): " + e.what(); - - if constexpr (NoThrow) return message; - else throw FormatException(message); - } -} - -template<size_t N> -struct str -{ -public: - const char s[N + 1]; - static constexpr size_t size = N; - -private: - template<size_t... indices> - constexpr str<N + 1> appendCharHelper(const char c, std::index_sequence<indices...>) const - { - return str<N + 1>{s[indices]..., c}; - } - - template<size_t... indices> - static constexpr str<N + 1> prependCharHelper(const char c, const str<N> &s, std::index_sequence<indices...>) - { - return str<N + 1>{c, s.s[indices]...}; - } - - template<auto other, size_t... ownIndices, size_t... othersIndices> - constexpr str<N + sizeof...(othersIndices)> appendStrHelper(std::index_sequence<ownIndices...>, std::index_sequence<othersIndices...>) const - { - return str<N + sizeof...(othersIndices)>{s[ownIndices]..., other.s[othersIndices]...}; - } - -public: - template<size_t... indices> - constexpr str(const char (&s)[N + 1], std::index_sequence<indices...>) : s{s[indices]...} - { - - } - - constexpr str(const char (&s)[N + 1]) : str{s, std::make_index_sequence<N + 1>()} - { - - } - - template<typename... Chars> - constexpr str(Chars... chars) : s{chars..., '\0'} - { - static_assert(N == sizeof...(chars), "O.o"); - } - - template<size_t index> - constexpr char get() const - { - static_assert(index < N, "str::get: index out of range"); - return s[index]; - } - - template<size_t offset, size_t... indices> - constexpr str<sizeof...(indices)> extract(std::index_sequence<indices...>) const - { - static_assert(((indices + offset < N) && ...), "str::extract: index out of range"); - return str<sizeof...(indices)>{s[indices + offset]...}; - } - - constexpr str<N + 1> operator+(const char c) const - { - return appendCharHelper(c, std::make_index_sequence<N>()); - } - - template<size_t otherN> - constexpr str<N + otherN> operator+(const str<otherN> &other) const - { - return appendStrHelper<other>(std::make_index_sequence<N>(), std::make_index_sequence<otherN>()); - } - - template<size_t _N> - friend constexpr str<_N + 1> operator+(const char c, const str<_N> &other); -}; - -template<size_t N> -constexpr str<N + 1> operator+(const char c, const str<N> &other) -{ - return str<N>::prependCharHelper(c, other, std::make_index_sequence<N>()); -} - -template<size_t N> -str(const char(&)[N]) -> str<N - 1>; - -template<typename... Chars> -str(Chars... chars) -> str<sizeof...(Chars)>; - -namespace detail -{ - template<size_t i, size_t N> - constexpr char get(const str<N>& s) - { - return s.template get<i>(); - } - - template<size_t N, size_t offset, size_t... indices> - constexpr str<sizeof...(indices)> extractStr(const str<N> &source, std::index_sequence<indices...>) - { - return str<sizeof...(indices)>{get<indices + offset>(source)...}; - } - - template<size_t start, size_t end = std::string::npos, bool endIsIndex = false, size_t N> - constexpr str<end == std::string::npos ? N - start : (endIsIndex ? end + 1 - start : end)> substr(const str<N> &s) - { - static_assert(!endIsIndex || end < N, "substr: end index / substr-length is out of range"); - if constexpr (!endIsIndex && end == 0) return str<0>{}; - else if constexpr (end == std::string::npos) return substr<start, N - 1, true>(s); - else if constexpr (!endIsIndex) return substr<start, start + end - 1, true>(s); - else return s.template extract<start>(std::make_index_sequence<end - start + 1>()); - } - - template<typename T, auto s, size_t i = 0, bool allowSign = true, T parsedPart = 0> - constexpr T strToInt() - { - if constexpr (i == s.size) return parsedPart; - else - { - constexpr char c = get<i>(s); - if constexpr (c == '-') - { - static_assert(allowSign, "strToInt: invalid character found"); - static_assert(std::is_signed_v<T>, "strToInt: negative sign found when converting to an unsigned type"); - return -strToInt<T, s, i + 1, false>(); - } - else if constexpr (c == '+') - { - static_assert(allowSign, "strToInt: invalid character found"); - return strToInt<T, s, i + 1, false>(); - } - else - { - static_assert(c >= '0' && c <= '9', "strToInt: invalid character found"); - return strToInt<T, s, i + 1, false, parsedPart * T{10} + T{c - '0'}>(); - } - } - } - - template<typename T, T N> - constexpr size_t decimalLength() - { - if constexpr (N < 0) return 1 + decimalLength<T, -N>(); - else if constexpr (N < 10) return 1; - else return 1 + decimalLength<T, N / 10>(); - } - - template<typename T, T N> - constexpr str<decimalLength<T, N>()> toStr() - { - if constexpr (N < 0) return '-' + toStr<T, -N>(); - else if constexpr (N < 10) - { - return str<1>{'0' + N}; - } - else return toStr<T, N / 10>() + char(N % 10 + '0'); - } - - template<auto fmt, size_t i, size_t argumentIndex> - std::string format_s() - { - if constexpr (i == fmt.size) return fmt.s; - else if constexpr (get<i>(fmt) == '%') - { - if constexpr (get<i + 1>(fmt) == '%') return substr<0, i, true>(fmt).s + format_s<substr<i + 2>(fmt), 0, argumentIndex>(); - else - { - static_assert(get<i + 1>(fmt) == '%', "Not enough arguments passed"); - throw FormatException{"Not enough arguments passed"}; - } - } - else return format_s<fmt, i + 1, argumentIndex>(); - } - - template<auto fmt, size_t i, size_t offset = 0> - constexpr size_t getFlagEnd() - { - constexpr char next = get<i + offset>(fmt); - if constexpr (next == '#' || next == '0' || next == '-' || next == ' ' || next == '+') return getFlagEnd<fmt, i, offset + 1>(); - else return i + offset; - } - - template<auto fmt, size_t i> - constexpr size_t getNumberEnd() - { - if constexpr (std::isdigit(get<i>(fmt))) return getNumberEnd<fmt, i + 1>(); - else return i; - } - - template<char conversion, size_t argumentIndex, typename Arg> - constexpr char checkReplaceAutoConversion() - { - if constexpr (conversion == 'v' || conversion == 'V') return autoConversionSpecifier<conversion == 'V', false, Arg>(argumentIndex); - else return conversion; - } - - template<bool havePrecision, auto fmt, size_t iAfterFieldWidth, size_t iAfterPrecision, typename String> - size_t determineStringPrecision(const String& string) - { - if constexpr (havePrecision) return std::min(strToInt<size_t, substr<iAfterFieldWidth + 1, iAfterPrecision - 1, true>(fmt)>(), string.size()); - else return string.size(); - } - - template<auto fmt, char conversion, size_t i, size_t iAfterPrecision, char length> - constexpr auto makeDirective() - { - if constexpr (length) return substr<i, iAfterPrecision - 1, true>(fmt) + length + conversion; - else return substr<i, iAfterPrecision - 1, true>(fmt) + conversion; - } - - template<char c, std::size_t size> - consteval void showUnknownConversionSpecifier() - { - static_assert(size == 0, "Unknown conversion specifier!"); - } - - template<auto fmt, size_t i, size_t argumentIndex, typename Arg, typename... Args> - std::string format_s(Arg &&arg, Args &&...args) - { - static_assert(i < fmt.size, "Too many arguments passed"); - if constexpr (i == fmt.size) - { - throw FormatException{std::to_string(argumentIndex - 1) + " placeholders found, but " + std::to_string(argumentIndex + sizeof...(args)) + " arguments passed"}; - } - else if constexpr (get<i>(fmt) == '%') - { - if constexpr (get<i + 1>(fmt) == '%') return substr<0, i, true>(fmt).s + format_s<substr<i + 2>(fmt), 0, argumentIndex>(std::forward<Arg>(arg), std::forward<Args>(args)...); - - constexpr char iAfterFlag = getFlagEnd<fmt, i + 1>(); - constexpr size_t iAfterFieldWidth = getNumberEnd<fmt, iAfterFlag>(); - - constexpr auto havePrecision = get<iAfterFieldWidth>(fmt) == '.'; - constexpr size_t iAfterPrecision = [havePrecision, iAfterFieldWidth]() -> size_t { if constexpr (havePrecision) return getNumberEnd<fmt, iAfterFieldWidth + 1>(); else return iAfterFieldWidth; }(); - - constexpr char conversion = checkReplaceAutoConversion<get<iAfterPrecision>(fmt), argumentIndex, Arg>(); - - if constexpr (conversion == 'c') return substr<0, i>(fmt).s + strprintf(makeDirective<fmt, conversion, i, iAfterPrecision, 0>().s, convert<char, false>(std::forward<Arg>(arg), "character", argumentIndex)) + format_s<substr<iAfterPrecision + 1>(fmt), 0, argumentIndex + 1>(std::forward<Args>(args)...); - else if constexpr (conversion == 'd' || conversion == 'i') return substr<0, i>(fmt).s + strprintf(makeDirective<fmt, conversion, i, iAfterPrecision, 'j'>().s, convert<std::intmax_t, false>(std::forward<Arg>(arg), "signed integer", argumentIndex)) + format_s<substr<iAfterPrecision + 1>(fmt), 0, argumentIndex + 1>(std::forward<Args>(args)...); - else if constexpr (conversion == 'u' || conversion == 'o' || conversion == 'x' || conversion == 'X') return substr<0, i>(fmt).s + strprintf(makeDirective<fmt, conversion, i, iAfterPrecision, 'j'>().s, convert<std::uintmax_t, false>(std::forward<Arg>(arg), "unsigned integer", argumentIndex)) + format_s<substr<iAfterPrecision + 1>(fmt), 0, argumentIndex + 1>(std::forward<Args>(args)...); - else if constexpr (conversion == 'e' || conversion == 'E' || conversion == 'f' || conversion == 'F' || conversion == 'g' || conversion == 'G' || conversion == 'a' || conversion == 'A') return substr<0, i>(fmt).s + strprintf(makeDirective<fmt, conversion, i, iAfterPrecision, 0>().s, convert<double, false>(std::forward<Arg>(arg), "double", argumentIndex)) + format_s<substr<iAfterPrecision + 1>(fmt), 0, argumentIndex + 1>(std::forward<Args>(args)...); - else if constexpr (conversion == 'p') return substr<0, i>(fmt).s + strprintf(makeDirective<fmt, conversion, i, iAfterPrecision, 0>().s, convert<const void *, false>(std::forward<Arg>(arg), "pointer", argumentIndex)) + format_s<substr<iAfterPrecision + 1>(fmt), 0, argumentIndex + 1>(std::forward<Args>(args)...); - else if constexpr (conversion == 's') - { - const auto& string = convert<string_or_string_view<std::remove_cvref_t<Arg>>, false>(std::forward<Arg>(arg), "string", argumentIndex); - return substr<0, i>(fmt).s + strprintf("%.*s", determineStringPrecision<havePrecision, fmt, iAfterFieldWidth, iAfterPrecision>(string), string.data()) + format_s<substr<iAfterPrecision + 1>(fmt), 0, argumentIndex + 1>(std::forward<Args>(args)...); - } - else - { - showUnknownConversionSpecifier<conversion, fmt.size>(); - throw FormatException{std::string{"Unknown conversion specifier: \""} + conversion + "\""}; - } - } - else return format_s<fmt, i + 1, argumentIndex>(std::forward<Arg>(arg), std::forward<Args>(args)...); - } -} - -template<str fmt, typename... Args> -std::string format(Args &&...args) -{ - return detail::format_s<fmt, 0, 1>(std::forward<Args>(args)...); -} -} |
