Skip to content

Instantly share code, notes, and snippets.

@jonwis
Last active August 5, 2024 03:37
Show Gist options
  • Save jonwis/1bddc0673b3a8ad027eb992042fa94b5 to your computer and use it in GitHub Desktop.
Save jonwis/1bddc0673b3a8ad027eb992042fa94b5 to your computer and use it in GitHub Desktop.
// 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;
}
#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;
}
@oldnewthing
Copy link

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.

#include <iostream>
#include <string>
#include <array>
#include <string_view>
#include <span>
#include <algorithm>
#include <charconv>
#include <intrin.h>

using namespace std::literals;

using format_to = void (*)(const void* thing, std::string_view fmt, std::string& target);

// You are allowed to add your own overload of append_formatted_to.
// If you want to templatize the first parameter, use the following
// prototype to avoid overload conflicts:
//
//template<typename T>
// void append_formatted_to(T const& value, std::enable_if_t<(YOUR CONDITION), std::string_view> fmt, std::string& result);
// 
// The traditional
// 
// template<typename T, typename = std::enable_if_t<(YOUR CONDITION)>>
// void append_formatted_to(T const& value, std::string_view fmt, std::string& result);
// 
// will result in redefinition conflicts because they will all be considered conflicting two-type templates.

template<typename T>
void append_formatted_to(T const& value, std::enable_if_t<std::is_convertible_v<T, std::string_view>, std::string_view> /*fmt*/, std::string& result)
{
    result.append(static_cast<std::string_view>(value));
}

void append_formatted_to(bool const& value, std::string_view fmt, std::string& result)
{
    append_formatted_to(value ? "true"sv : "false"sv, fmt, result);
}

template<typename T>
void append_formatted_to(T value, std::enable_if_t<std::is_arithmetic_v<T>, std::string_view> /*fmt*/, std::string& result)
{
    char buffer[32];
    auto [ptr, ec] = std::to_chars(std::begin(buffer), std::end(buffer), value);
    if (ec != std::errc{}) __fastfail(0);
    result.append(std::begin(buffer), ptr);
}

template<typename T>
format_to get_format_to()
{
    return [](const void* thing, std::string_view fmt, std::string& result)
        {
            auto& value = *reinterpret_cast<const std::remove_reference_t<T>*>(thing);
            return append_formatted_to(value, fmt, result);
        };
}

std::string myformat_2_worker(std::string_view fmt, std::span<const void*> handles, std::span<format_to> formatters)
{
    std::string result;
    std::size_t next = 0;

    while (!fmt.empty())
    {
        auto lbrace = std::find(fmt.begin(), fmt.end(), '{');

        auto rbrace = std::find(fmt.begin(), lbrace, '}');
        if (rbrace != lbrace)
        {
            // Close-brace without open-brace must be doubled.
            if (rbrace[1] != '}')
            {
                throw std::runtime_error("Unmatched close-brace");
            }
            // Emit up to and including the first close-brace.
            result.append(fmt.begin(), rbrace + 1);
            // Eat both of the close-braces.
            fmt.remove_prefix(rbrace + 2 - fmt.begin());
        }
        else if (lbrace != fmt.end())
        {
            // Double open brace?
            if (lbrace + 1 != fmt.end() && lbrace[1] == '{')
            {
                // Emit up to and including the first open-brace.
                result.append(fmt.begin(), lbrace + 1);
                // Eat both of the open-braces.
                fmt.remove_prefix(lbrace + 2 - fmt.begin());
                continue;
            }
            else
            {
                // Emit up to but not including the open-brace.
                result.append(fmt.begin(), rbrace);

                // Ensure open-brace is terminated with a close-brace.
                rbrace = std::find(lbrace, fmt.end(), '}');
                if (rbrace == fmt.end())
                {
                    throw std::runtime_error("Unmatched open-brace");
                }
                // Consume up to but not including the close-brace.
                fmt.remove_prefix(rbrace + 1 - fmt.begin());

                // We disallow double-braces inside a format. Sorry.

                if (next >= formatters.size())
                {
                    throw std::runtime_error("Not enough values");
                }
                formatters[next](handles[next], { lbrace + 1, rbrace }, result);
                next++;
            }
        }
        else
        {
            // Just a literal. Emit it.
            result.append(fmt.begin(), rbrace);
            break;
        }
    }

    return result;
}

template<typename... Args>
std::string myformat_2(std::string_view fmt, Args&&... args)
{
    if constexpr (sizeof...(args) == 0)
    {
        return myformat_2_worker(fmt, {}, {});
    }
    else
    {
        format_to formatters[] = { get_format_to<Args>()... };
        const void* handles[] = { std::addressof(args)... };
        return myformat_2_worker(fmt, handles, formatters);
    }
}

template<typename... Args>
void test_myformat(bool expected, std::string_view fmt, Args&&... args)
{
    std::cout << "Formatting [" << fmt << "] with " << sizeof...(args) << " insertions" << std::endl;
    bool actual = false;
    try
    {
        std::cout << myformat_2(fmt, std::forward<Args>(args)...) << std::endl;
        actual = true;
    }
    catch (std::exception const& ex)
    {
        std::cout << "Exception: " << ex.what() << std::endl;
    }
    std::cout << (actual == expected ? "PASS" : "FAIL") << std::endl;
}

int main()
{
    test_myformat(true, "hello");
    test_myformat(true, "{{hello}}");
    test_myformat(true, "}}{{hello");
    test_myformat(false, "}{{hello");
    test_myformat(false, "{hello");
    test_myformat(false, "}}{hello}");
    test_myformat(true, "}}{} and {}", 42, 3.141);
    test_myformat(true, "{}, {} and {}", "Hello", "world"sv, "done"s);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment