Skip to content

Commit

Permalink
Feature: Support for std::format (#228)
Browse files Browse the repository at this point in the history
  • Loading branch information
mingxwa authored Jan 9, 2025
1 parent 62c052e commit f34bb8f
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 0 deletions.
88 changes: 88 additions & 0 deletions proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
#include <type_traits>
#include <utility>

#if __STDC_HOSTED__
#include <format>
#endif // __STDC_HOSTED__

#ifdef __cpp_rtti
#include <optional>
#include <typeinfo>
Expand Down Expand Up @@ -554,6 +558,10 @@ struct facade_conv_traits_impl<F, Cs...> : applicable_traits {
template <class P>
static constexpr bool conv_applicable_ptr =
(conv_traits<Cs>::template applicable_ptr<P> && ...);
template <bool IsDirect, class D, class O>
static constexpr bool is_invocable = std::is_base_of_v<dispatcher_meta<
typename overload_traits<O>::template meta_provider<IsDirect, D>>,
conv_meta>;
};
template <class F, class... Rs>
struct facade_refl_traits_impl : inapplicable_traits {};
Expand Down Expand Up @@ -1575,6 +1583,47 @@ struct sign {
template <std::size_t N>
sign(const char (&str)[N]) -> sign<N>;

#if __STDC_HOSTED__
template <class CharT> struct format_overload_traits;
template <>
struct format_overload_traits<char>
: std::type_identity<std::format_context::iterator(
std::string_view spec, std::format_context& fc) const> {};
template <>
struct format_overload_traits<wchar_t>
: std::type_identity<std::wformat_context::iterator(
std::wstring_view spec, std::wformat_context& fc) const> {};
template <class CharT>
using format_overload_t = typename format_overload_traits<CharT>::type;

struct format_dispatch {
// Note: This function requires std::formatter<T, CharT> to be well-formed.
// However, the standard did not provide such facility before C++23. In the
// "required" clause of this function, std::formattable (C++23) is preferred
// when available. Otherwise, when building with C++20, we simply check
// whether std::formatter<T, CharT> is a disabled specialization of
// std::formatter by std::is_default_constructible_v as per
// [format.formatter.spec].
template <class T, class CharT, class OutIt>
OutIt operator()(const T& self, std::basic_string_view<CharT> spec,
std::basic_format_context<OutIt, CharT>& fc)
requires(
#if defined(__cpp_lib_format_ranges) && __cpp_lib_format_ranges >= 202207L
std::formattable<T, CharT>
#else
std::is_default_constructible_v<std::formatter<T, CharT>>
#endif // defined(__cpp_lib_format_ranges) && __cpp_lib_format_ranges >= 202207L
) {
std::formatter<T, CharT> impl;
{
std::basic_format_parse_context<CharT> pc{spec};
impl.parse(pc);
}
return impl.format(self, fc);
}
};
#endif // __STDC_HOSTED__

#ifdef __cpp_rtti
struct proxy_cast_context {
const std::type_info* type_ptr;
Expand Down Expand Up @@ -1737,6 +1786,12 @@ struct basic_facade_builder {
template <constraint_level CL>
using support_destruction = basic_facade_builder<
Cs, Rs, details::make_destructible(C, CL)>;
#if __STDC_HOSTED__
using support_format = add_convention<
details::format_dispatch, details::format_overload_t<char>>;
using support_wformat = add_convention<
details::format_dispatch, details::format_overload_t<wchar_t>>;
#endif // __STDC_HOSTED__
#ifdef __cpp_rtti
using support_indirect_rtti = basic_facade_builder<
details::add_conv_t<Cs, details::conv_impl<false,
Expand Down Expand Up @@ -2108,6 +2163,39 @@ ___PRO_DEBUG( \

} // namespace pro

#if __STDC_HOSTED__
namespace std {

template <class F, class CharT>
requires(pro::details::facade_traits<F>::template is_invocable<false,
pro::details::format_dispatch, pro::details::format_overload_t<CharT>>)
struct formatter<pro::proxy_indirect_accessor<F>, CharT> {
constexpr auto parse(basic_format_parse_context<CharT>& pc) {
for (auto it = pc.begin(); it != pc.end(); ++it) {
if (*it == '}') {
spec_ = basic_string_view<CharT>{pc.begin(), it + 1};
return it;
}
}
return pc.end();
}

template <class OutIt>
OutIt format(const pro::proxy_indirect_accessor<F>& ia,
basic_format_context<OutIt, CharT>& fc) const {
auto& p = pro::access_proxy<F>(ia);
if (!p.has_value()) { ___PRO_THROW(format_error{"null proxy"}); }
return pro::proxy_invoke<false, pro::details::format_dispatch,
pro::details::format_overload_t<CharT>>(p, spec_, fc);
}

private:
basic_string_view<CharT> spec_;
};

} // namespace std
#endif // __STDC_HOSTED__

#undef ___PRO_THROW
#undef ___PRO_NO_UNIQUE_ADDRESS_ATTRIBUTE

Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ FetchContent_MakeAvailable(googletest)
add_executable(msft_proxy_tests
proxy_creation_tests.cpp
proxy_dispatch_tests.cpp
proxy_format_tests.cpp
proxy_integration_tests.cpp
proxy_invocation_tests.cpp
proxy_lifetime_tests.cpp
Expand Down
60 changes: 60 additions & 0 deletions tests/proxy_format_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#include <gtest/gtest.h>
#include "proxy.h"

namespace proxy_format_tests_details {

struct NonFormattable : pro::facade_builder::build {};

static_assert(!std::is_default_constructible_v<std::formatter<pro::proxy_indirect_accessor<NonFormattable>, char>>);
static_assert(!std::is_default_constructible_v<std::formatter<pro::proxy_indirect_accessor<NonFormattable>, wchar_t>>);

struct Formattable : pro::facade_builder
::support_format
::support_wformat
::build {};

static_assert(std::is_default_constructible_v<std::formatter<pro::proxy_indirect_accessor<Formattable>, char>>);
static_assert(std::is_default_constructible_v<std::formatter<pro::proxy_indirect_accessor<Formattable>, wchar_t>>);

} // namespace proxy_format_tests_details

namespace details = proxy_format_tests_details;

TEST(ProxyFormatTests, TestFormat_Null) {
pro::proxy<details::Formattable> p;
bool exception_thrown = false;
try {
std::ignore = std::format("{}", *p);
} catch (const std::format_error&) {
exception_thrown = true;
}
ASSERT_TRUE(exception_thrown);
}

TEST(ProxyFormatTests, TestFormat_Value) {
int v = 123;
pro::proxy<details::Formattable> p = &v;
ASSERT_EQ(std::format("{}", *p), "123");
ASSERT_EQ(std::format("{:*<6}", *p), "123***");
}

TEST(ProxyFormatTests, TestWformat_Null) {
pro::proxy<details::Formattable> p;
bool exception_thrown = false;
try {
std::ignore = std::format(L"{}", *p);
} catch (const std::format_error&) {
exception_thrown = true;
}
ASSERT_TRUE(exception_thrown);
}

TEST(ProxyFormatTests, TestWformat_Value) {
int v = 123;
pro::proxy<details::Formattable> p = &v;
ASSERT_EQ(std::format(L"{}", *p), L"123");
ASSERT_EQ(std::format(L"{:*<6}", *p), L"123***");
}

0 comments on commit f34bb8f

Please sign in to comment.