diff options
| author | Markus Mittendrein <git@maxmitti.tk> | 2019-07-21 22:02:19 +0200 |
|---|---|---|
| committer | Markus Mittendrein <git@maxmitti.tk> | 2019-07-21 22:02:19 +0200 |
| commit | e483d06fe31a2569c58aed429fe6d69f4e7f208c (patch) | |
| tree | f8672f488e783fd196da3ed2cf366beb3927b2c1 | |
| parent | 17f1b653c38a2f9478aaf9d6d762d0c8dd9e6f6f (diff) | |
| download | cxxformat-e483d06fe31a2569c58aed429fe6d69f4e7f208c.tar.gz cxxformat-e483d06fe31a2569c58aed429fe6d69f4e7f208c.zip | |
Add compile time checked variant (needs C++20)
| -rw-r--r-- | CMakeLists.txt | 2 | ||||
| -rw-r--r-- | cxxformat.hpp | 269 | ||||
| -rw-r--r-- | main.cpp | 5 |
3 files changed, 266 insertions, 10 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f75a25..49b8ecc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.0) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) project(cxxformat) diff --git a/cxxformat.hpp b/cxxformat.hpp index b02fe4f..6a86e4c 100644 --- a/cxxformat.hpp +++ b/cxxformat.hpp @@ -52,7 +52,7 @@ template<typename From> struct FormatConvert<double, From, std::enable_if_t<std::is_convertible_v<From, double> && !std::is_integral_v<From>>> : ImplicitConversion<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>> && !std::is_same_v<std::remove_pointer_t<Pointer>, char>>> : ImplicitConversion<const void *, const 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> {}; @@ -60,7 +60,7 @@ struct FormatConvert<const void *, Pointer, std::enable_if_t<std::is_pointer_v<P template<typename From, typename Enable = void> struct AutoConversion { - static std::string conversion(const From &) + static bool conversion(const From &) { throw FormatException{""}; } @@ -69,9 +69,9 @@ struct AutoConversion template<char Conversion, typename From> struct SimpleAutoConversion { - static std::string conversion(const From &) + static constexpr char conversion(const From &) { - return std::string{Conversion}; + return Conversion; } }; @@ -201,11 +201,29 @@ namespace detail template<typename From> using string_or_string_view = typename StringOrStringViewHelper<From>::type; - template<bool allowPointer = false, typename From> - std::string autoConversionSpecifier(From &&from, std::size_t index = 0) + template<bool allowPointer = false, bool allowFallback = true, typename From> + constexpr char autoConversionSpecifier(From &&from, std::size_t index = 0) { try { + if constexpr (!allowFallback) + { + if constexpr(!std::is_same_v<char, decltype(AutoConversion<remove_cvref_t<From>>::conversion(std::forward<From>(from)))>) + { + 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<remove_cvref_t<From>>::conversion(std::forward<From>(from)); } catch (const FormatException &) @@ -214,10 +232,11 @@ namespace detail { if constexpr (allowPointer) { - return "p"; + 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"}; } } @@ -378,4 +397,240 @@ std::string format(const String &fmt, Args &&... args) } } +template<size_t i> +struct str_index { static constexpr size_t n = i; }; + +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, '\0'}; + } + + 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]..., '\0'}; + } + + 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]..., '\0'}; + } +public: + + template<size_t index> + constexpr char get(str_index<index>) 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...>, str_index<offset> = str_index<0>{}) const + { + static_assert(((indices + offset < N) && ...), "str::extract: index out of range"); + return str<sizeof...(indices)>{s[indices + offset]..., '\0'}; + } + + 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>; + +namespace detail +{ + template<size_t i, size_t N> + constexpr char get(const str<N>& s) + { + return s.get(str_index<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)..., '\0'}; + } + + 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, "End index / substr-length is out of range"); + 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 if constexpr (endIsIndex && end + 1 - start == 0) return str{""}; + else return s.extract(std::make_index_sequence<end - start + 1>(), str_index<start>{}); + } + + 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 ""; + 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> + constexpr std::pair<char, size_t> getFlag() + { + constexpr char next = get<i>(fmt); + if constexpr (next == '#' || next == '0' || next == '-' || next == ' ' || next == '+') return {next, i + 1}; + else return {0, i}; + } + + 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(Arg &&arg) + { + if constexpr (conversion == 'v' || conversion == 'V') return autoConversionSpecifier<conversion == 'V', false>(std::forward<Arg>(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<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 auto flagData = getFlag<fmt, i + 1>(); + constexpr char flag = flagData.first; + constexpr char iAfterFlag = flagData.second; + 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>(std::forward<Arg>(arg)); + + if constexpr (conversion == 'c') return substr<0, i - 1, true>(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 - 1, true>(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 - 1, true>(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 - 1, true>(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 - 1, true>(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<remove_cvref_t<Arg>>, false>(std::forward<Arg>(arg), "string", argumentIndex); + return substr<0, i - 1, true>(fmt).s + strprintf((str{"%."}.s + std::to_string(determineStringPrecision<havePrecision, fmt, iAfterFieldWidth, iAfterPrecision>(string)) + 's').c_str(), string.data()) + format_s<substr<iAfterPrecision + 1>(fmt), 0, argumentIndex + 1>(std::forward<Args>(args)...); + } + else + { + static_assert(fmt.size == 0, "Unknown conversion specifier!"); + 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<auto fmt, typename... Args> +std::string format_s(Args &&...args) +{ + return detail::format_s<fmt, 0, 1>(std::forward<Args>(args)...); +} }
\ No newline at end of file @@ -1,8 +1,8 @@ #include "cxxformat.hpp" #include <array> +#include <utility> #include <iostream> - template<size_t N, typename T> struct format::FormatConvert<std::string, T[N], std::enable_if_t<!std::is_same_v<format::detail::remove_cvref_t<T>, char>>> { @@ -42,8 +42,9 @@ struct format::AutoConversion<T[N], std::enable_if_t<!std::is_same_v<format::det int main(int argc, char *argv[]) { - std::cout << format::format("%v%v%v%v%v", 'H', 'a', 'l', 'l', 'o') << std::endl; const std::array<int, 5> array[] = {std::array{1, 2, 3, 4, 5}, std::array{1, 2, 3, 4, 5}, std::array{1, 2, 3, 4, 5}, std::array{1, 2, 3, 4, 5}, std::array{1, 2, 3, 4, 5}}; + std::cout << format::format_s<format::str{"Hello %s: %d %03.5f %V"}>("World", 7, 3.5, &array) << std::endl; + std::cout << format::format("%v%v%v%v%v", 'H', 'e', 'l', 'l', 'o') << std::endl; std::cout << format::format("%V", &array) << std::endl; std::cout << format::format("%s", std::array{1, 2, 3, 4, 5}) << std::endl; std::cout << format::format("Hello %v: %-7v %v", "World", 3.5, 5) << std::endl; |
