From f34bb8fd4ad5df7b5fb9e61be81dd99ce95039ee Mon Sep 17 00:00:00 2001 From: Mingxin Wang Date: Thu, 9 Jan 2025 16:50:33 +0800 Subject: [PATCH] Feature: Support for `std::format` (#228) --- proxy.h | 88 ++++++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/proxy_format_tests.cpp | 60 ++++++++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 tests/proxy_format_tests.cpp diff --git a/proxy.h b/proxy.h index 31692a6..b2189cc 100644 --- a/proxy.h +++ b/proxy.h @@ -17,6 +17,10 @@ #include #include +#if __STDC_HOSTED__ +#include +#endif // __STDC_HOSTED__ + #ifdef __cpp_rtti #include #include @@ -554,6 +558,10 @@ struct facade_conv_traits_impl : applicable_traits { template static constexpr bool conv_applicable_ptr = (conv_traits::template applicable_ptr

&& ...); + template + static constexpr bool is_invocable = std::is_base_of_v::template meta_provider>, + conv_meta>; }; template struct facade_refl_traits_impl : inapplicable_traits {}; @@ -1575,6 +1583,47 @@ struct sign { template sign(const char (&str)[N]) -> sign; +#if __STDC_HOSTED__ +template struct format_overload_traits; +template <> +struct format_overload_traits + : std::type_identity {}; +template <> +struct format_overload_traits + : std::type_identity {}; +template +using format_overload_t = typename format_overload_traits::type; + +struct format_dispatch { + // Note: This function requires std::formatter 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 is a disabled specialization of + // std::formatter by std::is_default_constructible_v as per + // [format.formatter.spec]. + template + OutIt operator()(const T& self, std::basic_string_view spec, + std::basic_format_context& fc) + requires( +#if defined(__cpp_lib_format_ranges) && __cpp_lib_format_ranges >= 202207L + std::formattable +#else + std::is_default_constructible_v> +#endif // defined(__cpp_lib_format_ranges) && __cpp_lib_format_ranges >= 202207L + ) { + std::formatter impl; + { + std::basic_format_parse_context 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; @@ -1737,6 +1786,12 @@ struct basic_facade_builder { template 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>; + using support_wformat = add_convention< + details::format_dispatch, details::format_overload_t>; +#endif // __STDC_HOSTED__ #ifdef __cpp_rtti using support_indirect_rtti = basic_facade_builder< details::add_conv_t + requires(pro::details::facade_traits::template is_invocable>) +struct formatter, CharT> { + constexpr auto parse(basic_format_parse_context& pc) { + for (auto it = pc.begin(); it != pc.end(); ++it) { + if (*it == '}') { + spec_ = basic_string_view{pc.begin(), it + 1}; + return it; + } + } + return pc.end(); + } + + template + OutIt format(const pro::proxy_indirect_accessor& ia, + basic_format_context& fc) const { + auto& p = pro::access_proxy(ia); + if (!p.has_value()) { ___PRO_THROW(format_error{"null proxy"}); } + return pro::proxy_invoke>(p, spec_, fc); + } + + private: + basic_string_view spec_; +}; + +} // namespace std +#endif // __STDC_HOSTED__ + #undef ___PRO_THROW #undef ___PRO_NO_UNIQUE_ADDRESS_ATTRIBUTE diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 49a6987..d5070c9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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 diff --git a/tests/proxy_format_tests.cpp b/tests/proxy_format_tests.cpp new file mode 100644 index 0000000..c5beab2 --- /dev/null +++ b/tests/proxy_format_tests.cpp @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include +#include "proxy.h" + +namespace proxy_format_tests_details { + +struct NonFormattable : pro::facade_builder::build {}; + +static_assert(!std::is_default_constructible_v, char>>); +static_assert(!std::is_default_constructible_v, wchar_t>>); + +struct Formattable : pro::facade_builder + ::support_format + ::support_wformat + ::build {}; + +static_assert(std::is_default_constructible_v, char>>); +static_assert(std::is_default_constructible_v, wchar_t>>); + +} // namespace proxy_format_tests_details + +namespace details = proxy_format_tests_details; + +TEST(ProxyFormatTests, TestFormat_Null) { + pro::proxy 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 p = &v; + ASSERT_EQ(std::format("{}", *p), "123"); + ASSERT_EQ(std::format("{:*<6}", *p), "123***"); +} + +TEST(ProxyFormatTests, TestWformat_Null) { + pro::proxy 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 p = &v; + ASSERT_EQ(std::format(L"{}", *p), L"123"); + ASSERT_EQ(std::format(L"{:*<6}", *p), L"123***"); +}