Last active
August 5, 2024 03:37
-
-
Save jonwis/1bddc0673b3a8ad027eb992042fa94b5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is replacement for std::format on systems that don't have std::locale support, | |
// but do have std::to_chars locale invariant. | |
template <typename... Args> | |
std::string myformat(std::format_string<Args...> fmt, Args&&... args) | |
{ | |
std::string output; | |
auto formatArgStore = std::make_format_args(args...); | |
auto formatArgs = std::format_args(formatArgStore); | |
std::string_view fmtString = fmt.get(); | |
int nextIndex = 0; | |
while (!fmtString.empty()) | |
{ | |
auto openBrace = fmtString.find('{'); | |
if (openBrace == std::string_view::npos) | |
{ | |
output.append(fmtString); | |
break; | |
} | |
output.append(fmtString.substr(0, openBrace)); | |
fmtString.remove_prefix(openBrace + 1); | |
auto closeBrace = fmtString.find('}'); | |
if (closeBrace == std::string_view::npos) | |
{ | |
output.append(fmtString); | |
break; | |
} | |
// Right now, we require that the caller provide the arguments in order. | |
std::visit_format_arg([&](auto&& arg) { | |
using T = std::decay_t<decltype(arg)>; | |
if constexpr (std::is_convertible_v<T, std::string_view>) | |
{ | |
output.append(arg); | |
} | |
else if constexpr (std::is_same_v<T, std::monostate>) | |
{ | |
// Do nothing | |
} | |
else if constexpr (std::is_same_v<decltype(arg), const void*&>) | |
{ | |
char buffer[32]; | |
auto [ptr, ec] = std::to_chars(buffer, buffer + sizeof(buffer), reinterpret_cast<unsigned long long>(arg), 16); | |
output.append(buffer, ptr - buffer); | |
} | |
else if constexpr (std::is_same_v<T, bool>) | |
{ | |
output.append(arg ? "true" : "false"); | |
} | |
else if constexpr (std::is_scalar_v<T>) | |
{ | |
char buffer[32]; | |
auto [ptr, ec] = std::to_chars(buffer, buffer + sizeof(buffer), arg); | |
output.append(buffer, ptr - buffer); | |
} | |
else if constexpr (std::is_same_v<T, std::basic_format_arg<std::format_context>::handle>) | |
{ | |
throw std::invalid_argument("Custom formatters not supported"); | |
} | |
else | |
{ | |
static_assert(std::is_convertible_v<T, std::string_view>, "oopsie" __FUNCSIG__); | |
} | |
}, formatArgs.get(nextIndex++)); | |
fmtString.remove_prefix(closeBrace + 1); | |
} | |
return output; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <string> | |
#include <string_view> | |
#include <iostream> | |
#include <span> | |
#include <charconv> | |
#include <variant> | |
using format_to_t = void (*)(const void* thing, std::string_view formatString, std::string& target); | |
template<typename T> void format_to(const void* thing, std::string_view, std::string& target) | |
{ | |
using TReal = std::decay_t<T>; | |
if constexpr (std::is_convertible_v<T, const char*>) | |
{ | |
static_assert(false, "Don't use raw strings, use std::string_view"); | |
} | |
else if constexpr (std::is_convertible_v<TReal, std::string_view>) | |
{ | |
target.append(*reinterpret_cast<const TReal*>(thing)); | |
} | |
else if constexpr (std::is_same_v<TReal, bool>) | |
{ | |
target.append(*reinterpret_cast<const TReal*>(thing) ? "true" : "false"); | |
} | |
else if constexpr (std::is_pointer_v<std::remove_reference_t<T>>) | |
{ | |
char buffer[32]; | |
auto [ptr, ec] = std::to_chars(buffer, buffer + sizeof(buffer), reinterpret_cast<uintptr_t>(*reinterpret_cast<const TReal*>(thing)), 16); | |
target.append(buffer, ptr - buffer); | |
} | |
else if constexpr (std::is_floating_point_v<TReal>) | |
{ | |
char buffer[32]; | |
auto [ptr, ec] = std::to_chars(buffer, buffer + sizeof(buffer), *reinterpret_cast<const TReal*>(thing)); | |
target.append(buffer, ptr - buffer); | |
} | |
else if constexpr (std::is_scalar_v<TReal>) | |
{ | |
char buffer[32]; | |
auto [ptr, ec] = std::to_chars(buffer, buffer + sizeof(buffer), *reinterpret_cast<const TReal*>(thing), 10); | |
target.append(buffer, ptr - buffer); | |
} | |
else | |
{ | |
static_assert(!std::is_same_v<T, T>, "Unsupported type " __FUNCSIG__); | |
} | |
} | |
std::string myformat_2_worker(std::string_view fmt, std::span<const void*> handles, std::span<format_to_t> formatters) | |
{ | |
std::string result; | |
int next = 0; | |
while (!fmt.empty()) | |
{ | |
size_t brace = fmt.find('{'); | |
if (brace == std::string_view::npos) | |
{ | |
result.append(fmt); | |
break; | |
} | |
result.append(fmt.substr(0, brace)); | |
fmt.remove_prefix(brace + 1); | |
size_t closeBrace = fmt.find('}'); | |
if (closeBrace == std::string_view::npos) | |
{ | |
throw std::runtime_error("Unmatched brace"); | |
} | |
formatters[next](handles[next], fmt.substr(0, closeBrace), result); | |
fmt.remove_prefix(closeBrace + 1); | |
next++; | |
} | |
return result; | |
} | |
template<typename... Args> | |
std::string myformat_2(std::string_view fmt, Args&&... args) | |
{ | |
format_to_t formatters[] = {&format_to<Args>...}; | |
const void* handles[] = { std::addressof(args)... }; | |
return myformat_2_worker(fmt, handles, formatters); | |
} | |
int main() | |
{ | |
std::string s = "Hello"; | |
int* p = reinterpret_cast<int*>(0x12345678); | |
std::string result = myformat_2("s={0} bool={1} ptr={2} int={3} str={4}", s, false, p, 16.2f, std::string_view{ "kittens" }, std::tuple<int, int>{}); | |
std::cout << result << std::endl; | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
My variation. Fixes some bugs (std::decay converts char[6] to char*; doesn't handle curly brace edge cases or edge case of 0 insertions), makes it easier to add support for new types. Need to fix it so it runs on C++17.