Skip to content

Commit ca65d6d

Browse files
authored
Refactor the format skills (#35)
1 parent 70f13ad commit ca65d6d

8 files changed

Lines changed: 144 additions & 140 deletions

File tree

CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ if (BUILD_TESTING)
110110
)
111111
FetchContent_MakeAvailable(fmt)
112112

113+
if(MSVC)
114+
set(PROXY_WARNING_FLAGS /W4)
115+
set(PROXY_STRICT_WARNING_FLAGS ${PROXY_WARNING_FLAGS} /WX)
116+
else()
117+
set(PROXY_WARNING_FLAGS -Wall -Wextra -Wpedantic $<$<CXX_COMPILER_ID:Clang>:-Wno-c++2b-extensions>)
118+
set(PROXY_STRICT_WARNING_FLAGS ${PROXY_WARNING_FLAGS} -Werror)
119+
endif()
120+
113121
add_subdirectory(tests)
114122
add_subdirectory(benchmarks)
115123
add_subdirectory(docs)

benchmarks/CMakeLists.txt

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,4 @@ add_executable(msft_proxy_benchmarks
1616
)
1717
target_include_directories(msft_proxy_benchmarks PRIVATE .)
1818
target_link_libraries(msft_proxy_benchmarks PRIVATE msft_proxy4::proxy benchmark::benchmark benchmark::benchmark_main)
19-
20-
if (MSVC)
21-
target_compile_options(msft_proxy_benchmarks PRIVATE /W4)
22-
else()
23-
target_compile_options(msft_proxy_benchmarks PRIVATE -Wall -Wextra -Wpedantic $<$<CXX_COMPILER_ID:Clang>:-Wno-c++2b-extensions>)
24-
endif()
19+
target_compile_options(msft_proxy_benchmarks PRIVATE ${PROXY_WARNING_FLAGS})

docs/CMakeLists.txt

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,7 @@ foreach(SOURCE ${EXAMPLE_SOURCES})
1818
get_filename_component(EXECUTABLE_NAME ${SOURCE} NAME_WE)
1919
add_executable(${EXECUTABLE_NAME} ${SOURCE})
2020
target_link_libraries(${EXECUTABLE_NAME} PRIVATE msft_proxy4::proxy)
21-
if (MSVC)
22-
target_compile_options(${EXECUTABLE_NAME} PRIVATE /W4)
23-
else()
24-
target_compile_options(${EXECUTABLE_NAME} PRIVATE -Wall -Wextra -Wpedantic $<$<CXX_COMPILER_ID:Clang>:-Wno-c++2b-extensions>)
25-
endif()
21+
target_compile_options(${EXECUTABLE_NAME} PRIVATE ${PROXY_WARNING_FLAGS})
2622
endforeach()
2723

2824
target_link_libraries(example_spec_skills_fmt_format PRIVATE fmt::fmt)

include/proxy/v4/proxy.h

Lines changed: 82 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
#include <tuple>
1919
#include <type_traits>
2020
#include <utility>
21+
#include <version>
2122

2223
#if __STDC_HOSTED__
2324
#include <atomic>
24-
#if __has_include(<format>)
25+
26+
// LLVM libc++ 17 has usable <format> despite lacking __cpp_lib_format until 19.
27+
#if __cpp_lib_format >= 201907L || _LIBCPP_VERSION >= 170000
2528
#include <format>
26-
#endif // __has_include(<format>)
27-
#if __cpp_lib_format >= 201907L || \
28-
(defined(_LIBCPP_VERSION) && _LIBCPP_VERSION >= 170000)
2929
#define PRO4D_HAS_FORMAT
3030
#endif // __cpp_lib_format || _LIBCPP_VERSION >= 170000
3131
#endif // __STDC_HOSTED__
@@ -714,9 +714,6 @@ struct facade_conv_traits_impl {
714714
template <class P>
715715
static constexpr bool conv_applicable_ptr =
716716
(conv_traits<Cs, F>::template applicable_ptr<P> && ...);
717-
template <bool IsDirect, class D, class O>
718-
static constexpr bool is_invocable =
719-
std::is_base_of_v<invocation_meta<F, IsDirect, D, O>, conv_meta>;
720717
};
721718
template <class F, class... Rs>
722719
struct facade_refl_traits_impl {
@@ -2148,6 +2145,11 @@ class bad_proxy_cast : public std::bad_cast {
21482145

21492146
namespace details {
21502147

2148+
template <template <class...> class TT, class... Ctx>
2149+
struct enabled_t {};
2150+
template <class T, template <class...> class TT, class... Ctx>
2151+
concept enabled_for = std::is_base_of_v<enabled_t<TT, Ctx...>, T>;
2152+
21512153
struct view_conversion_dispatch : cast_dispatch_base<false, true> {
21522154
template <class T>
21532155
PRO4D_STATIC_CALL(auto, T& value) noexcept
@@ -2179,47 +2181,74 @@ struct weak_conversion_dispatch : cast_dispatch_base<false, true> {
21792181
template <class F>
21802182
using weak_conversion_overload = weak_proxy<F>() const noexcept;
21812183

2184+
template <template <class...> class Formatter,
2185+
template <class...> class StringView,
2186+
template <class...> class ParseContext,
2187+
template <class...> class FormatContext>
2188+
struct format_traits {
2189+
template <class CharT>
2190+
using overload = typename FormatContext<CharT>::iterator(
2191+
StringView<CharT> spec, FormatContext<CharT>& fc) const;
2192+
2193+
struct dispatch {
2194+
template <class T, class CharT>
2195+
PRO4D_STATIC_CALL(auto, const T& self, StringView<CharT> spec,
2196+
FormatContext<CharT>& fc)
2197+
requires(std::is_default_constructible_v<Formatter<T, CharT>>)
2198+
{
2199+
Formatter<T, CharT> impl;
2200+
{
2201+
ParseContext<CharT> pc{spec};
2202+
impl.parse(pc);
2203+
}
2204+
return impl.format(self, fc);
2205+
}
2206+
2207+
template <class P, class D, class... Os>
2208+
struct PRO4D_ENFORCE_EBO accessor : accessor<P, D, Os>... {};
2209+
template <class P, class D>
2210+
struct accessor<P, D, overload<char>> : enabled_t<Formatter, char> {};
2211+
template <class P, class D>
2212+
struct accessor<P, D, overload<wchar_t>> : enabled_t<Formatter, wchar_t> {};
2213+
};
2214+
2215+
template <class CharT>
2216+
struct formatter {
2217+
constexpr auto parse(ParseContext<CharT>& pc) {
2218+
for (auto it = pc.begin(); it != pc.end(); ++it) {
2219+
if (*it == '}') {
2220+
spec_ = StringView<CharT>{pc.begin(), it + 1};
2221+
return it;
2222+
}
2223+
}
2224+
return pc.end();
2225+
}
2226+
2227+
template <class P, class CompatibleFormatContext>
2228+
auto format(const P& p, CompatibleFormatContext& fc) const ->
2229+
typename CompatibleFormatContext::iterator {
2230+
return invoke<dispatch, overload<CharT>>(p, spec_, fc);
2231+
}
2232+
2233+
private:
2234+
StringView<CharT> spec_;
2235+
};
2236+
};
2237+
21822238
#ifdef PRO4D_HAS_FORMAT
21832239
template <class CharT>
2184-
struct format_overload_traits;
2240+
struct std_format_context_traits;
21852241
template <>
2186-
struct format_overload_traits<char>
2187-
: std::type_identity<std::format_context::iterator(
2188-
std::string_view spec, std::format_context& fc) const> {};
2242+
struct std_format_context_traits<char>
2243+
: std::type_identity<std::format_context> {};
21892244
template <>
2190-
struct format_overload_traits<wchar_t>
2191-
: std::type_identity<std::wformat_context::iterator(
2192-
std::wstring_view spec, std::wformat_context& fc) const> {};
2245+
struct std_format_context_traits<wchar_t>
2246+
: std::type_identity<std::wformat_context> {};
21932247
template <class CharT>
2194-
using format_overload_t = typename format_overload_traits<CharT>::type;
2195-
2196-
struct format_dispatch {
2197-
// Note: This function requires std::formatter<T, CharT> to be well-formed.
2198-
// However, the standard did not provide such facility before C++23. In the
2199-
// "required" clause of this function, std::formattable (C++23) is preferred
2200-
// when available. Otherwise, when building with C++20, we simply check
2201-
// whether std::formatter<T, CharT> is a disabled specialization of
2202-
// std::formatter by std::is_default_constructible_v as per
2203-
// [format.formatter.spec].
2204-
template <class T, class CharT, class OutIt>
2205-
PRO4D_STATIC_CALL(OutIt, const T& self, std::basic_string_view<CharT> spec,
2206-
std::basic_format_context<OutIt, CharT>& fc)
2207-
requires(
2208-
#if __cpp_lib_format_ranges >= 202207L
2209-
std::formattable<T, CharT>
2210-
#else
2211-
std::is_default_constructible_v<std::formatter<T, CharT>>
2212-
#endif // __cpp_lib_format_ranges >= 202207L
2213-
)
2214-
{
2215-
std::formatter<T, CharT> impl;
2216-
{
2217-
std::basic_format_parse_context<CharT> pc{spec};
2218-
impl.parse(pc);
2219-
}
2220-
return impl.format(self, fc);
2221-
}
2222-
};
2248+
using std_format_context = typename std_format_context_traits<CharT>::type;
2249+
struct std_format_traits
2250+
: format_traits<std::formatter, std::basic_string_view,
2251+
std::basic_format_parse_context, std_format_context> {};
22232252
#endif // PRO4D_HAS_FORMAT
22242253

22252254
#if __cpp_rtti >= 199711L
@@ -2329,14 +2358,14 @@ namespace skills {
23292358

23302359
#ifdef PRO4D_HAS_FORMAT
23312360
template <class FB>
2332-
using format =
2333-
typename FB::template add_convention<details::format_dispatch,
2334-
details::format_overload_t<char>>;
2361+
using format = typename FB::template add_convention<
2362+
details::std_format_traits::dispatch,
2363+
details::std_format_traits::overload<char>>;
23352364

23362365
template <class FB>
2337-
using wformat =
2338-
typename FB::template add_convention<details::format_dispatch,
2339-
details::format_overload_t<wchar_t>>;
2366+
using wformat = typename FB::template add_convention<
2367+
details::std_format_traits::dispatch,
2368+
details::std_format_traits::overload<wchar_t>>;
23402369
#endif // PRO4D_HAS_FORMAT
23412370

23422371
#if __cpp_rtti >= 199711L
@@ -2676,31 +2705,10 @@ struct weak_dispatch : D {
26762705
#ifdef PRO4D_HAS_FORMAT
26772706
namespace std {
26782707

2679-
template <pro::v4::facade F, class CharT>
2680-
requires(pro::v4::details::facade_traits<F>::template is_invocable<
2681-
false, pro::v4::details::format_dispatch,
2682-
pro::v4::details::format_overload_t<CharT>>)
2683-
struct formatter<pro::v4::proxy_indirect_accessor<F>, CharT> {
2684-
constexpr auto parse(basic_format_parse_context<CharT>& pc) {
2685-
for (auto it = pc.begin(); it != pc.end(); ++it) {
2686-
if (*it == '}') {
2687-
spec_ = basic_string_view<CharT>{pc.begin(), it + 1};
2688-
return it;
2689-
}
2690-
}
2691-
return pc.end();
2692-
}
2693-
2694-
template <class OutIt>
2695-
OutIt format(const pro::v4::proxy_indirect_accessor<F>& p,
2696-
basic_format_context<OutIt, CharT>& fc) const {
2697-
return invoke<pro::v4::details::format_dispatch,
2698-
pro::v4::details::format_overload_t<CharT>>(p, spec_, fc);
2699-
}
2700-
2701-
private:
2702-
basic_string_view<CharT> spec_;
2703-
};
2708+
template <class T, class CharT>
2709+
requires(pro::v4::details::enabled_for<T, std::formatter, CharT>)
2710+
struct formatter<T, CharT>
2711+
: pro::v4::details::std_format_traits::formatter<CharT> {};
27042712

27052713
} // namespace std
27062714
#endif // PRO4D_HAS_FORMAT

include/proxy/v4/proxy_fmt.h

Lines changed: 12 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -28,69 +28,34 @@ using fmt_buffered_context = fmt::buffered_context<CharT>;
2828
using fmt_buffered_context = fmt::buffer_context<CharT>;
2929
#endif // FMT_VERSION
3030

31-
template <class CharT>
32-
using fmt_format_overload_t = fmt_buffered_context<CharT>::iterator(
33-
std::basic_string_view<CharT> spec, fmt_buffered_context<CharT>& fc) const;
34-
35-
struct fmt_format_dispatch {
36-
template <class T, class CharT, class FormatContext>
37-
PRO4D_STATIC_CALL(auto, const T& self, std::basic_string_view<CharT> spec,
38-
FormatContext& fc)
39-
requires(std::is_default_constructible_v<fmt::formatter<T, CharT>>)
40-
{
41-
fmt::formatter<T, CharT> impl;
42-
{
43-
fmt::basic_format_parse_context<CharT> pc{spec};
44-
impl.parse(pc);
45-
}
46-
return impl.format(self, fc);
47-
}
48-
};
31+
struct fmt_format_traits
32+
: format_traits<fmt::formatter, std::basic_string_view,
33+
fmt::basic_format_parse_context, fmt_buffered_context> {};
4934

5035
} // namespace details
5136

5237
namespace skills {
5338

5439
template <class FB>
55-
using fmt_format =
56-
typename FB::template add_convention<details::fmt_format_dispatch,
57-
details::fmt_format_overload_t<char>>;
40+
using fmt_format = typename FB::template add_convention<
41+
details::fmt_format_traits::dispatch,
42+
details::fmt_format_traits::overload<char>>;
5843

5944
template <class FB>
6045
using fmt_wformat = typename FB::template add_convention<
61-
details::fmt_format_dispatch, details::fmt_format_overload_t<wchar_t>>;
46+
details::fmt_format_traits::dispatch,
47+
details::fmt_format_traits::overload<wchar_t>>;
6248

6349
} // namespace skills
6450

6551
} // namespace pro::inline v4
6652

6753
namespace fmt {
6854

69-
template <pro::v4::facade F, class CharT>
70-
requires(pro::v4::details::facade_traits<F>::template is_invocable<
71-
false, pro::v4::details::fmt_format_dispatch,
72-
pro::v4::details::fmt_format_overload_t<CharT>>)
73-
struct formatter<pro::v4::proxy_indirect_accessor<F>, CharT> {
74-
constexpr auto parse(basic_format_parse_context<CharT>& pc) {
75-
for (auto it = pc.begin(); it != pc.end(); ++it) {
76-
if (*it == '}') {
77-
spec_ = std::basic_string_view<CharT>{pc.begin(), it + 1};
78-
return it;
79-
}
80-
}
81-
return pc.end();
82-
}
83-
84-
template <class FormatContext>
85-
auto format(const pro::v4::proxy_indirect_accessor<F>& p,
86-
FormatContext& fc) const -> typename FormatContext::iterator {
87-
return invoke<pro::v4::details::fmt_format_dispatch,
88-
pro::v4::details::fmt_format_overload_t<CharT>>(p, spec_, fc);
89-
}
90-
91-
private:
92-
std::basic_string_view<CharT> spec_;
93-
};
55+
template <class T, class CharT>
56+
requires(pro::v4::details::enabled_for<T, fmt::formatter, CharT>)
57+
struct formatter<T, CharT>
58+
: pro::v4::details::fmt_format_traits::formatter<CharT> {};
9459

9560
} // namespace fmt
9661

tests/CMakeLists.txt

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ add_executable(msft_proxy_tests
1414
proxy_creation_tests.cpp
1515
proxy_details_tests.cpp
1616
proxy_dispatch_tests.cpp
17+
proxy_fmt_format_no_wchar_tests.cpp
1718
proxy_fmt_format_tests.cpp
1819
proxy_format_tests.cpp
1920
proxy_integration_tests.cpp
@@ -27,18 +28,12 @@ add_executable(msft_proxy_tests
2728
)
2829
target_include_directories(msft_proxy_tests PRIVATE .)
2930
target_link_libraries(msft_proxy_tests PRIVATE msft_proxy4::proxy gtest_main fmt::fmt)
30-
31-
if(MSVC)
32-
target_compile_options(msft_proxy_tests PRIVATE /W4 /WX)
33-
else()
34-
target_compile_options(msft_proxy_tests PRIVATE -Wall -Wextra -Wpedantic -Werror $<$<CXX_COMPILER_ID:Clang>:-Wno-c++2b-extensions>)
35-
endif()
36-
31+
target_compile_options(msft_proxy_tests PRIVATE ${PROXY_STRICT_WARNING_FLAGS})
3732
gtest_discover_tests(msft_proxy_tests)
3833

3934
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
4035
add_executable(msft_proxy_freestanding_tests freestanding/proxy_freestanding_tests.cpp)
41-
target_compile_options(msft_proxy_freestanding_tests PRIVATE -ffreestanding -fno-exceptions -fno-rtti -Wall -Wextra -Wpedantic -Werror $<$<CXX_COMPILER_ID:Clang>:-Wno-c++2b-extensions>)
36+
target_compile_options(msft_proxy_freestanding_tests PRIVATE -ffreestanding -fno-exceptions -fno-rtti ${PROXY_STRICT_WARNING_FLAGS})
4237
target_link_options(msft_proxy_freestanding_tests PRIVATE -nodefaultlibs -lc)
4338
target_link_libraries(msft_proxy_freestanding_tests PRIVATE msft_proxy4::proxy)
4439
add_test(NAME ProxyFreestandingTests COMMAND msft_proxy_freestanding_tests)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) 2022-2026 Microsoft Corporation.
2+
// Copyright (c) 2026-Present Next Gen C++ Foundation.
3+
// Licensed under the MIT License.
4+
5+
#include <gtest/gtest.h>
6+
7+
#include <fmt/format.h> // <fmt/xchar.h> is intentionally not included in this TU
8+
#include <proxy/proxy.h>
9+
#include <proxy/proxy_fmt.h>
10+
11+
namespace proxy_fmt_format_no_wchar_tests_details {
12+
13+
struct NonFormattable : pro::facade_builder::build {};
14+
15+
static_assert(
16+
!std::is_default_constructible_v<
17+
fmt::formatter<pro::proxy_indirect_accessor<NonFormattable>, char>>);
18+
19+
struct Formattable : pro::facade_builder //
20+
::add_skill<pro::skills::fmt_format> //
21+
::build {};
22+
23+
static_assert(std::is_default_constructible_v<
24+
fmt::formatter<pro::proxy_indirect_accessor<Formattable>, char>>);
25+
26+
} // namespace proxy_fmt_format_no_wchar_tests_details
27+
28+
namespace details = proxy_fmt_format_no_wchar_tests_details;
29+
30+
TEST(ProxyFmtFormatNoWcharTests, TestFormat) {
31+
int v = 123;
32+
pro::proxy<details::Formattable> p = &v;
33+
ASSERT_EQ(fmt::format("{}", *p), "123");
34+
ASSERT_EQ(fmt::format("{:*<6}", *p), "123***");
35+
}

0 commit comments

Comments
 (0)