Skip to content

Commit 2f97d92

Browse files
committed
✨ Add STDX_CT_FORMAT
Problem: - It's easy to call `stdx::ct_format` but forget to preserve the constexpr nature of the arguments. Especially with literals, e.g. `stdx::ct_format<"{}">(42)` results in a runtime format argument. Solution: - Add `STDX_CT_FORMAT` which automatically wraps the arguments so that their constexpr nature is preserved where possible. `STDX_CT_FORMAT("{}", 42)` is now a compile-time formatting operation.
1 parent 97c4bd9 commit 2f97d92

File tree

3 files changed

+103
-25
lines changed

3 files changed

+103
-25
lines changed

include/stdx/ct_format.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <stdx/concepts.hpp>
77
#include <stdx/ct_conversions.hpp>
88
#include <stdx/ct_string.hpp>
9+
#include <stdx/pp_map.hpp>
910
#include <stdx/tuple.hpp>
1011
#include <stdx/tuple_algorithms.hpp>
1112
#include <stdx/type_traits.hpp>
@@ -259,4 +260,11 @@ constexpr auto num_fmt_specifiers =
259260
} // namespace v1
260261
} // namespace stdx
261262

263+
// NOLINTBEGIN(cppcoreguidelines-macro-usage)
264+
265+
#define STDX_CT_FORMAT(S, ...) \
266+
stdx::ct_format<S>(STDX_MAP(CX_WRAP __VA_OPT__(, ) __VA_ARGS__))
267+
268+
// NOLINTEND(cppcoreguidelines-macro-usage)
269+
262270
#endif

include/stdx/utility.hpp

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ struct from_any {
148148
struct type_val {
149149
template <typename T, typename U,
150150
typename = std::enable_if_t<same_as_unqualified<type_val, U>>>
151-
friend constexpr auto operator+(T t, U &&) -> T {
151+
friend constexpr auto operator+(T t, U const &) -> T {
152152
return t;
153153
}
154154
friend constexpr auto operator+(type_val const &f) -> type_val { return f; }
@@ -238,20 +238,21 @@ template <typename T> constexpr auto is_ct_v<T const> = is_ct_v<T>;
238238
STDX_PRAGMA(diagnostic push) \
239239
STDX_PRAGMA(diagnostic ignored "-Wold-style-cast") \
240240
STDX_PRAGMA(diagnostic ignored "-Wunused-value") \
241-
if constexpr (decltype(stdx::cxv_detail::is_type< \
242-
stdx::cxv_detail::from_any( \
241+
if constexpr (decltype(::stdx::cxv_detail::is_type< \
242+
::stdx::cxv_detail::from_any( \
243243
__VA_ARGS__)>())::value) { \
244-
return stdx::overload{stdx::cxv_detail::cx_base{}, [&] { \
245-
return stdx::type_identity< \
246-
decltype(stdx::cxv_detail::type_of< \
247-
stdx::cxv_detail::from_any( \
248-
__VA_ARGS__)>())>{}; \
249-
}}; \
244+
return ::stdx::overload{ \
245+
::stdx::cxv_detail::cx_base{}, [&] { \
246+
return ::stdx::type_identity< \
247+
decltype(::stdx::cxv_detail::type_of< \
248+
::stdx::cxv_detail::from_any( \
249+
__VA_ARGS__)>())>{}; \
250+
}}; \
250251
} else { \
251-
return stdx::overload{stdx::cxv_detail::cx_base{}, [&] { \
252-
return (__VA_ARGS__) + \
253-
stdx::cxv_detail::type_val{}; \
254-
}}; \
252+
return ::stdx::overload{::stdx::cxv_detail::cx_base{}, [&] { \
253+
return (__VA_ARGS__) + \
254+
::stdx::cxv_detail::type_val{}; \
255+
}}; \
255256
} \
256257
STDX_PRAGMA(diagnostic pop) \
257258
}()
@@ -272,26 +273,49 @@ template <typename T> constexpr auto is_ct_v<T const> = is_ct_v<T>;
272273
} \
273274
}([&] { return X; })
274275

276+
#ifdef __clang__
277+
#define CX_DETECT(X) std::is_empty_v<decltype(CX_VALUE(X))>
278+
#else
279+
namespace stdx {
280+
inline namespace v1 {
281+
template <auto> constexpr auto cx_detect0() {}
282+
constexpr auto cx_detect1(auto) { return 0; }
283+
} // namespace v1
284+
} // namespace stdx
285+
#define CX_DETECT(X) \
286+
requires { \
287+
::stdx::cx_detect0<::stdx::cx_detect1( \
288+
(X) + ::stdx::cxv_detail::type_val{})>; \
289+
}
290+
#endif
291+
275292
#define CX_WRAP(X) \
276-
[&]<typename F>(F) { \
293+
[&]<typename F>(F f) { \
277294
STDX_PRAGMA(diagnostic push) \
278295
STDX_PRAGMA(diagnostic ignored "-Wold-style-cast") \
279296
if constexpr (::stdx::is_cx_value_v<std::invoke_result_t<F>>) { \
280-
return (X) + ::stdx::cxv_detail::type_val{}; \
281-
} else if constexpr (requires { \
282-
::stdx::detail::cx_detect0< \
283-
::stdx::detail::cx_detect1( \
284-
CX_VALUE(X)())>; \
285-
}) { \
286-
return CX_VALUE(X); \
297+
return f(); \
298+
} else if constexpr (CX_DETECT(X)) { \
299+
if constexpr (decltype(::stdx::cxv_detail::is_type< \
300+
::stdx::cxv_detail::from_any( \
301+
X)>())::value) { \
302+
return ::stdx::overload{ \
303+
::stdx::cxv_detail::cx_base{}, [&] { \
304+
return ::stdx::type_identity< \
305+
decltype(::stdx::cxv_detail::type_of< \
306+
::stdx::cxv_detail::from_any(X)>())>{}; \
307+
}}; \
308+
} else { \
309+
return ::stdx::overload{::stdx::cxv_detail::cx_base{}, f}; \
310+
} \
287311
} else { \
288-
return (X) + ::stdx::cxv_detail::type_val{}; \
312+
return f(); \
289313
} \
290314
STDX_PRAGMA(diagnostic pop) \
291315
}([&] { \
292316
STDX_PRAGMA(diagnostic push) \
293317
STDX_PRAGMA(diagnostic ignored "-Wold-style-cast") \
294-
return (X) + stdx::cxv_detail::type_val{}; \
318+
return (X) + ::stdx::cxv_detail::type_val{}; \
295319
STDX_PRAGMA(diagnostic pop) \
296320
})
297321

test/ct_format.cpp

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,52 @@ constexpr auto ct_format_as(S const &s) {
242242
} // namespace user
243243

244244
TEST_CASE("user-defined formatting", "[ct_format]") {
245-
auto r = stdx::ct_format<"Hello {}">(user::S{42});
246-
CHECK(r == stdx::format_result{"Hello S: {}"_ctst, stdx::make_tuple(42)});
245+
auto r = stdx::ct_format<"Hello {}">(user::S{17});
246+
CHECK(r == stdx::format_result{"Hello S: {}"_ctst, stdx::make_tuple(17)});
247+
}
248+
249+
TEST_CASE("FORMAT with no arguments", "[ct_format]") {
250+
STATIC_REQUIRE(STDX_CT_FORMAT("Hello") == "Hello"_fmt_res);
251+
}
252+
253+
TEST_CASE("FORMAT a compile-time string argument", "[ct_format]") {
254+
STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", "world") ==
255+
"Hello world"_fmt_res);
256+
}
257+
258+
TEST_CASE("FORMAT a compile-time int argument", "[ct_format]") {
259+
STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", 17) == "Hello 17"_fmt_res);
260+
}
261+
262+
TEST_CASE("FORMAT a type argument", "[ct_format]") {
263+
STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", int) == "Hello int"_fmt_res);
264+
}
265+
266+
TEST_CASE("FORMAT a constexpr string argument", "[ct_format]") {
267+
constexpr static auto S = "world"_cts;
268+
STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", S) == "Hello world"_fmt_res);
269+
}
270+
271+
TEST_CASE("FORMAT a constexpr int argument", "[ct_format]") {
272+
constexpr static auto I = 17;
273+
STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", I) == "Hello 17"_fmt_res);
274+
}
275+
276+
#ifdef __clang__
277+
TEST_CASE("FORMAT a constexpr nonstatic string_view argument", "[ct_format]") {
278+
constexpr auto S = std::string_view{"world"};
279+
constexpr auto expected =
280+
stdx::format_result{"Hello {}"_ctst, stdx::make_tuple(S)};
281+
STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", S) == expected);
282+
}
283+
#endif
284+
285+
TEST_CASE("FORMAT a constexpr string_view argument", "[ct_format]") {
286+
constexpr static auto S = std::string_view{"world"};
287+
STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", S) == "Hello world"_fmt_res);
288+
}
289+
290+
TEST_CASE("FORMAT an integral_constant argument", "[ct_format]") {
291+
constexpr static auto I = std::integral_constant<int, 17>{};
292+
STATIC_REQUIRE(STDX_CT_FORMAT("Hello {}", I) == "Hello 17"_fmt_res);
247293
}

0 commit comments

Comments
 (0)