From 1972ba2b3b5d807772d2eda86f13af0ef6326a09 Mon Sep 17 00:00:00 2001 From: Ben Deane Date: Thu, 10 Jul 2025 11:44:58 -0600 Subject: [PATCH] :art: Improve `STATIC_ASSERT` Problem: - Now that `STDX_CT_FORMAT` exists, `STATIC_ASSERT` is clunky when it still has to wrap arguments with `CX_VALUE`. - Docs for the latest `ct_string` and `ct_format` functionality are lacking. Solution: - Use `STDX_CT_FORMAT` inside `STATIC_ASSERT` for a better experience. - Update the `ct_string` and `ct_format` docs. --- docs/ct_format.adoc | 32 +++++++++++++++++++++++++++++- docs/ct_string.adoc | 16 +++++++++++++++ docs/static_assert.adoc | 18 +++++++++++++---- include/stdx/static_assert.hpp | 30 ++++++++++------------------ test/fail/static_assert_format.cpp | 2 +- 5 files changed, 72 insertions(+), 26 deletions(-) diff --git a/docs/ct_format.adoc b/docs/ct_format.adoc index 61b5349..11efde0 100644 --- a/docs/ct_format.adoc +++ b/docs/ct_format.adoc @@ -1,6 +1,8 @@ == `ct_format.hpp` +=== ct_format + https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/ct_format.hpp[`ct_format.hpp`] provides `ct_format`, a compile-time function for formatting strings. @@ -20,7 +22,7 @@ auto s = stdx::ct_format<"Hello {} {}">(42, 17); ---- When format arguments are available at compile-time, wrapping them in -`CX_VALUE(...)` means they will get compile-time formatted. +xref:utility.adoc#_cx_value[`CX_VALUE(...)`] means they will get compile-time formatted. [source,cpp] ---- auto s = stdx::ct_format<"Hello {} {}">(CX_VALUE(42), 17); @@ -69,3 +71,31 @@ constexpr static auto a = stdx::ct_format<"The answer is {}">(42); constexpr static auto q = stdx::ct_format<"{}. But what is the question?">(CX_VALUE(a)); // q is stdx::format_result{"The answer is {}. But what is the question?"_ctst, stdx::tuple{42}} ---- + +=== STDX_CT_FORMAT + +The macro `STDX_CT_FORMAT` will automatically wrap compile-time-friendly +arguments, so manual wrapping with `CX_VALUE` is not required. +[source,cpp] +---- +auto s = STDX_CT_FORMAT("Hello {} {}", 42, int); +// equivalent to stdx::ct_format<"Hello {} {}">(CX_VALUE(42), CX_VALUE(int)); +// s is stdx::format_result{"Hello 42 int"_ctst, stdx::tuple{}} +---- + +If any arguments are _not_ available at compile time, they will be "regular" runtime format arguments. +[source,cpp] +---- +auto x = 42; +auto s = STDX_CT_FORMAT("Hello {} {}", x, int); +// equivalent to stdx::ct_format<"Hello {} {}">(x, CX_VALUE(int)); +// s is stdx::format_result{"Hello {} int"_ctst, stdx::tuple{42}} +---- + +Things that are "compile-time-friendly" include: + +* `constexpr static` variables +* `const` integral variables +* template arguments +* literals +* types diff --git a/docs/ct_string.adoc b/docs/ct_string.adoc index f37d25c..96bae02 100644 --- a/docs/ct_string.adoc +++ b/docs/ct_string.adoc @@ -97,3 +97,19 @@ constexpr auto s = "hello"_ctst; // s is a stdx::cts_t<"hello"> ---- Think of `cts_t` relating to `ct_string` as `std::integral_constant` relates to `int`. + +A `cts_t` is implicitly convertible to a `ct_string`, or can be explicitly +converted with unary `operator+`: + + +[source,cpp] +---- +using namespace stdx::literals; + +template +constexpr bool always_true = true; + +constexpr auto s = "hello"_ctst; +static_assert(always_true); // implicit conversion +static_assert(always_true<+s>); // explicit conversion with + +---- diff --git a/docs/static_assert.adoc b/docs/static_assert.adoc index 4165e48..87c3388 100644 --- a/docs/static_assert.adoc +++ b/docs/static_assert.adoc @@ -8,15 +8,13 @@ template constexpr auto f() { STATIC_ASSERT(std::is_integral, - "f() must take an integral type, received {}", CX_VALUE(T)); + "f() must take an integral type, received {}", T); } f(); // produces compile-time error ---- -The arguments to be formatted (if any) must be wrapped in -xref:utility.adoc#_cx_value[`CX_VALUE`] if they are not admissible as template -arguments. +NOTE: The arguments to be formatted must be compile-time, of course. The output from this (which varies by compiler) will contain the formatted string, and could be something like: @@ -33,3 +31,15 @@ include/stdx/static_assert.hpp:16:18: note: because NOTE: clang produces these "string-formatted" errors from version 15 onwards; GCC produces them from version 13.2 onwards. + +After C++26, +https://en.cppreference.com/w/cpp/language/static_assert.html[`static_assert`] +in the language means that formatted `STATIC_ASSERT` produces a slightly nicer +error message; something like: + +[source,bash] +---- +main.cpp:14:27: error: static assertion failed: f() must take an integral type, received float + 16 | STATIC_ASSERT(std::is_integral, + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +---- diff --git a/include/stdx/static_assert.hpp b/include/stdx/static_assert.hpp index abf8c64..958498b 100644 --- a/include/stdx/static_assert.hpp +++ b/include/stdx/static_assert.hpp @@ -22,41 +22,31 @@ template <> struct ct_check_t { } }; template constexpr auto ct_check = ct_check_t{}; - -namespace detail { -template constexpr auto static_format() { - constexpr auto make_ct = []() { - if constexpr (fmt_cx_value) { - return V; - } else { - return CX_VALUE(V); - } - }; - return ct_format(make_ct.template operator()()...).str.value; -} -} // namespace detail - } // namespace v1 } // namespace stdx #if __cpp_static_assert >= 202306L + // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define STATIC_ASSERT(cond, ...) \ []() -> bool { \ STDX_PRAGMA(diagnostic push) \ STDX_PRAGMA(diagnostic ignored "-Wunknown-warning-option") \ STDX_PRAGMA(diagnostic ignored "-Wc++26-extensions") \ - static_assert( \ - B, std::string_view{stdx::detail::static_format<__VA_ARGS__>()}); \ + constexpr auto S = STDX_CT_FORMAT(__VA_ARGS__); \ + static_assert(B, std::string_view{+S.str}); \ STDX_PRAGMA(diagnostic pop) \ return B; \ }.template operator()() + #else + // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define STATIC_ASSERT(cond, ...) \ - []() -> bool { \ - stdx::ct_check.template emit()>(); \ - return B; \ +#define STATIC_ASSERT(cond, ...) \ + []() -> bool { \ + constexpr auto S = STDX_CT_FORMAT(__VA_ARGS__); \ + stdx::ct_check.template emit(); \ + return B; \ }.template operator()() #endif diff --git a/test/fail/static_assert_format.cpp b/test/fail/static_assert_format.cpp index a8d5512..431349d 100644 --- a/test/fail/static_assert_format.cpp +++ b/test/fail/static_assert_format.cpp @@ -4,7 +4,7 @@ // EXPECT: hello world int 123 template constexpr auto f() { - STATIC_ASSERT(false, "hello {} {} {}", CX_VALUE("world"), CX_VALUE(T), 123); + STATIC_ASSERT(false, "hello {} {} {}", "world", T, 123); } auto main() -> int { f(); }