Skip to content
This repository was archived by the owner on Jan 29, 2026. It is now read-only.

Commit f34bb8f

Browse files
authored
Feature: Support for std::format (#228)
1 parent 62c052e commit f34bb8f

3 files changed

Lines changed: 149 additions & 0 deletions

File tree

proxy.h

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
#include <type_traits>
1818
#include <utility>
1919

20+
#if __STDC_HOSTED__
21+
#include <format>
22+
#endif // __STDC_HOSTED__
23+
2024
#ifdef __cpp_rtti
2125
#include <optional>
2226
#include <typeinfo>
@@ -554,6 +558,10 @@ struct facade_conv_traits_impl<F, Cs...> : applicable_traits {
554558
template <class P>
555559
static constexpr bool conv_applicable_ptr =
556560
(conv_traits<Cs>::template applicable_ptr<P> && ...);
561+
template <bool IsDirect, class D, class O>
562+
static constexpr bool is_invocable = std::is_base_of_v<dispatcher_meta<
563+
typename overload_traits<O>::template meta_provider<IsDirect, D>>,
564+
conv_meta>;
557565
};
558566
template <class F, class... Rs>
559567
struct facade_refl_traits_impl : inapplicable_traits {};
@@ -1575,6 +1583,47 @@ struct sign {
15751583
template <std::size_t N>
15761584
sign(const char (&str)[N]) -> sign<N>;
15771585

1586+
#if __STDC_HOSTED__
1587+
template <class CharT> struct format_overload_traits;
1588+
template <>
1589+
struct format_overload_traits<char>
1590+
: std::type_identity<std::format_context::iterator(
1591+
std::string_view spec, std::format_context& fc) const> {};
1592+
template <>
1593+
struct format_overload_traits<wchar_t>
1594+
: std::type_identity<std::wformat_context::iterator(
1595+
std::wstring_view spec, std::wformat_context& fc) const> {};
1596+
template <class CharT>
1597+
using format_overload_t = typename format_overload_traits<CharT>::type;
1598+
1599+
struct format_dispatch {
1600+
// Note: This function requires std::formatter<T, CharT> to be well-formed.
1601+
// However, the standard did not provide such facility before C++23. In the
1602+
// "required" clause of this function, std::formattable (C++23) is preferred
1603+
// when available. Otherwise, when building with C++20, we simply check
1604+
// whether std::formatter<T, CharT> is a disabled specialization of
1605+
// std::formatter by std::is_default_constructible_v as per
1606+
// [format.formatter.spec].
1607+
template <class T, class CharT, class OutIt>
1608+
OutIt operator()(const T& self, std::basic_string_view<CharT> spec,
1609+
std::basic_format_context<OutIt, CharT>& fc)
1610+
requires(
1611+
#if defined(__cpp_lib_format_ranges) && __cpp_lib_format_ranges >= 202207L
1612+
std::formattable<T, CharT>
1613+
#else
1614+
std::is_default_constructible_v<std::formatter<T, CharT>>
1615+
#endif // defined(__cpp_lib_format_ranges) && __cpp_lib_format_ranges >= 202207L
1616+
) {
1617+
std::formatter<T, CharT> impl;
1618+
{
1619+
std::basic_format_parse_context<CharT> pc{spec};
1620+
impl.parse(pc);
1621+
}
1622+
return impl.format(self, fc);
1623+
}
1624+
};
1625+
#endif // __STDC_HOSTED__
1626+
15781627
#ifdef __cpp_rtti
15791628
struct proxy_cast_context {
15801629
const std::type_info* type_ptr;
@@ -1737,6 +1786,12 @@ struct basic_facade_builder {
17371786
template <constraint_level CL>
17381787
using support_destruction = basic_facade_builder<
17391788
Cs, Rs, details::make_destructible(C, CL)>;
1789+
#if __STDC_HOSTED__
1790+
using support_format = add_convention<
1791+
details::format_dispatch, details::format_overload_t<char>>;
1792+
using support_wformat = add_convention<
1793+
details::format_dispatch, details::format_overload_t<wchar_t>>;
1794+
#endif // __STDC_HOSTED__
17401795
#ifdef __cpp_rtti
17411796
using support_indirect_rtti = basic_facade_builder<
17421797
details::add_conv_t<Cs, details::conv_impl<false,
@@ -2108,6 +2163,39 @@ ___PRO_DEBUG( \
21082163

21092164
} // namespace pro
21102165

2166+
#if __STDC_HOSTED__
2167+
namespace std {
2168+
2169+
template <class F, class CharT>
2170+
requires(pro::details::facade_traits<F>::template is_invocable<false,
2171+
pro::details::format_dispatch, pro::details::format_overload_t<CharT>>)
2172+
struct formatter<pro::proxy_indirect_accessor<F>, CharT> {
2173+
constexpr auto parse(basic_format_parse_context<CharT>& pc) {
2174+
for (auto it = pc.begin(); it != pc.end(); ++it) {
2175+
if (*it == '}') {
2176+
spec_ = basic_string_view<CharT>{pc.begin(), it + 1};
2177+
return it;
2178+
}
2179+
}
2180+
return pc.end();
2181+
}
2182+
2183+
template <class OutIt>
2184+
OutIt format(const pro::proxy_indirect_accessor<F>& ia,
2185+
basic_format_context<OutIt, CharT>& fc) const {
2186+
auto& p = pro::access_proxy<F>(ia);
2187+
if (!p.has_value()) { ___PRO_THROW(format_error{"null proxy"}); }
2188+
return pro::proxy_invoke<false, pro::details::format_dispatch,
2189+
pro::details::format_overload_t<CharT>>(p, spec_, fc);
2190+
}
2191+
2192+
private:
2193+
basic_string_view<CharT> spec_;
2194+
};
2195+
2196+
} // namespace std
2197+
#endif // __STDC_HOSTED__
2198+
21112199
#undef ___PRO_THROW
21122200
#undef ___PRO_NO_UNIQUE_ADDRESS_ATTRIBUTE
21132201

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ FetchContent_MakeAvailable(googletest)
1919
add_executable(msft_proxy_tests
2020
proxy_creation_tests.cpp
2121
proxy_dispatch_tests.cpp
22+
proxy_format_tests.cpp
2223
proxy_integration_tests.cpp
2324
proxy_invocation_tests.cpp
2425
proxy_lifetime_tests.cpp

tests/proxy_format_tests.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
#include <gtest/gtest.h>
5+
#include "proxy.h"
6+
7+
namespace proxy_format_tests_details {
8+
9+
struct NonFormattable : pro::facade_builder::build {};
10+
11+
static_assert(!std::is_default_constructible_v<std::formatter<pro::proxy_indirect_accessor<NonFormattable>, char>>);
12+
static_assert(!std::is_default_constructible_v<std::formatter<pro::proxy_indirect_accessor<NonFormattable>, wchar_t>>);
13+
14+
struct Formattable : pro::facade_builder
15+
::support_format
16+
::support_wformat
17+
::build {};
18+
19+
static_assert(std::is_default_constructible_v<std::formatter<pro::proxy_indirect_accessor<Formattable>, char>>);
20+
static_assert(std::is_default_constructible_v<std::formatter<pro::proxy_indirect_accessor<Formattable>, wchar_t>>);
21+
22+
} // namespace proxy_format_tests_details
23+
24+
namespace details = proxy_format_tests_details;
25+
26+
TEST(ProxyFormatTests, TestFormat_Null) {
27+
pro::proxy<details::Formattable> p;
28+
bool exception_thrown = false;
29+
try {
30+
std::ignore = std::format("{}", *p);
31+
} catch (const std::format_error&) {
32+
exception_thrown = true;
33+
}
34+
ASSERT_TRUE(exception_thrown);
35+
}
36+
37+
TEST(ProxyFormatTests, TestFormat_Value) {
38+
int v = 123;
39+
pro::proxy<details::Formattable> p = &v;
40+
ASSERT_EQ(std::format("{}", *p), "123");
41+
ASSERT_EQ(std::format("{:*<6}", *p), "123***");
42+
}
43+
44+
TEST(ProxyFormatTests, TestWformat_Null) {
45+
pro::proxy<details::Formattable> p;
46+
bool exception_thrown = false;
47+
try {
48+
std::ignore = std::format(L"{}", *p);
49+
} catch (const std::format_error&) {
50+
exception_thrown = true;
51+
}
52+
ASSERT_TRUE(exception_thrown);
53+
}
54+
55+
TEST(ProxyFormatTests, TestWformat_Value) {
56+
int v = 123;
57+
pro::proxy<details::Formattable> p = &v;
58+
ASSERT_EQ(std::format(L"{}", *p), L"123");
59+
ASSERT_EQ(std::format(L"{:*<6}", *p), L"123***");
60+
}

0 commit comments

Comments
 (0)