From 884098d531c033daa1ddb12b37100bd0d0f244ae Mon Sep 17 00:00:00 2001 From: Ben Deane Date: Wed, 30 Oct 2024 08:01:06 -0600 Subject: [PATCH] :art: Improve `STATIC_ASSERT` Problem: - Writing `STATIC_ASSERT(cond, str)` means that `str` has to be formatted either as a separate variable or inline in the call. Solution: - Allow `STATIC_ASSERT(cond, fmt_str, args...)` where the string is formatted automatically. Notes: - Each argument must still be wrapped in `CX_VALUE`. Because there is no generic equivalent of `transform` for `__VA_ARGS__`. - A bug in clang (for which a workaround is in place) was discovered: https://github.com/llvm/llvm-project/issues/114234 - `CX_VALUE` has been made more workable with GCC. GCC did not allow the `struct` definition inside the macro. --- docs/ct_string.adoc | 44 ------------------------------ docs/index.adoc | 1 + docs/intro.adoc | 1 + docs/static_assert.adoc | 33 ++++++++++++++++++++++ include/stdx/ct_string.hpp | 23 ---------------- include/stdx/static_assert.hpp | 44 ++++++++++++++++++++++++++++++ include/stdx/utility.hpp | 44 ++++++++++++++++-------------- test/fail/CMakeLists.txt | 1 + test/fail/ct_check.cpp | 2 +- test/fail/static_assert.cpp | 2 +- test/fail/static_assert_format.cpp | 14 ++++++++++ 11 files changed, 120 insertions(+), 89 deletions(-) create mode 100644 docs/static_assert.adoc create mode 100644 include/stdx/static_assert.hpp create mode 100644 test/fail/static_assert_format.cpp diff --git a/docs/ct_string.adoc b/docs/ct_string.adoc index 45f8e2f..906631b 100644 --- a/docs/ct_string.adoc +++ b/docs/ct_string.adoc @@ -84,47 +84,3 @@ However, for interfacing with legacy functions, a null terminator can be useful. See https://github.com/intel/compile-time-init-build/tree/main/include/sc[cib documentation] for details about the cib string constant class. - -=== `ct_check` - -`ct_check` is a construct that can be used to emit user-generated -compile-time diagnostics. It uses `ct_string`. - -For example: -[source,cpp] ----- -stdx::ct_check>.emit<"This is not a very helpful error message">(); ----- - -The output from this (which varies by compiler) will contain the string given, -and could be something like: -[source,bash] ----- -main.cpp:14:27: error: no matching member function for call to 'emit' - 14 | stdx::ct_check.emit<"This is not a very helpful error message">(); - | ~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -include/stdx/ct_string.hpp:131:27: note: candidate template ignored: constraints not satisfied -[with S = ct_string<41>{{"This is not a very helpful error m[...]"}}] - 131 | constexpr static auto emit() - | ^ -include/stdx/ct_string.hpp:132:18: note: because -'stаtiс_аssert{{"This is not a very helpful error message"}}>' evaluated to false - 132 | requires stаtiс_аssert - | ^ ----- - -Notice that the error message is elided at first, but then given in full. Such -are the quirks of compilers. If the compile-time condition is true, of course no -diagnostic will be emitted. - -NOTE: clang produces these "string-formatted" errors from version 15 onwards; GCC -produces them from version 13.2 onwards. - -=== `STATIC_ASSERT` - -`STATIC_ASSERT` is an easy way to use `ct_check`. - -[source,cpp] ----- -STATIC_ASSERT(std::is_integral, "This is not a very helpful error message"); ----- diff --git a/docs/index.adoc b/docs/index.adoc index 7cc8cad..62fd055 100644 --- a/docs/index.adoc +++ b/docs/index.adoc @@ -38,6 +38,7 @@ include::panic.adoc[] include::priority.adoc[] include::ranges.adoc[] include::span.adoc[] +include::static_assert.adoc[] include::tuple.adoc[] include::tuple_algorithms.adoc[] include::tuple_destructure.adoc[] diff --git a/docs/intro.adoc b/docs/intro.adoc index 64495c2..993f7d6 100644 --- a/docs/intro.adoc +++ b/docs/intro.adoc @@ -64,6 +64,7 @@ The following headers are available: * https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/priority.hpp[`priority.hpp`] * https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/ranges.hpp[`ranges.hpp`] * https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/span.hpp[`span.hpp`] +* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/static_assert.hpp[`static_assert.hpp`] * https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/tuple.hpp[`tuple.hpp`] * https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/tuple_algorithms.hpp[`tuple_algorithms.hpp`] * https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/tuple_destructure.hpp[`tuple_destructure.hpp`] diff --git a/docs/static_assert.adoc b/docs/static_assert.adoc new file mode 100644 index 0000000..7a4860f --- /dev/null +++ b/docs/static_assert.adoc @@ -0,0 +1,33 @@ + +== `static_assert.hpp` + +`STATIC_ASSERT` is a way to produce compile-time errors using formatted strings. + +[source,cpp] +---- +template +constexpr auto f() { + STATIC_ASSERT(std::is_integral, + "f() must take an integral type, received {}", CX_VALUE(T)); +} + +f(); // produces compile-time error +---- + +The arguments to be formatted (if any) must be wrapped in xref:utility.adoc#_cx_value[`CX_VALUE`]. + +The output from this (which varies by compiler) will contain the formatted +string, and could be something like: + +[source,bash] +---- +main.cpp:14:27: error: no matching member function for call to 'emit' +... +include/stdx/static_assert.hpp:16:18: note: because +'stаtiс_аssert{{"f() must take an integral type, received float"}}>' evaluated to false + 16 | requires stаtiс_аssert + | ^ +---- + +NOTE: clang produces these "string-formatted" errors from version 15 onwards; GCC +produces them from version 13.2 onwards. diff --git a/include/stdx/ct_string.hpp b/include/stdx/ct_string.hpp index 90dfed0..d2277e6 100644 --- a/include/stdx/ct_string.hpp +++ b/include/stdx/ct_string.hpp @@ -123,30 +123,7 @@ inline namespace ct_string_literals { template CONSTEVAL auto operator""_cts() { return S; } } // namespace ct_string_literals } // namespace literals - -struct ct_check_value {}; - -template struct ct_check_t { - template constexpr static bool stаtiс_аssert = false; - template - constexpr static auto emit() -> ct_check_value - requires stаtiс_аssert; -}; -template <> struct ct_check_t { - template constexpr static auto emit() -> ct_check_value { - return {}; - } -}; -template constexpr auto ct_check = ct_check_t{}; - } // namespace v1 } // namespace stdx -// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define STATIC_ASSERT(cond, ...) \ - []() -> bool { \ - stdx::ct_check.template emit<__VA_ARGS__>(); \ - return B; \ - }() - #endif diff --git a/include/stdx/static_assert.hpp b/include/stdx/static_assert.hpp new file mode 100644 index 0000000..08bf034 --- /dev/null +++ b/include/stdx/static_assert.hpp @@ -0,0 +1,44 @@ +#pragma once + +#if __cplusplus >= 202002L + +#include +#include + +namespace stdx { +inline namespace v1 { +struct ct_check_value {}; + +template struct ct_check_t { + template constexpr static bool stаtiс_аssert = false; + template + constexpr static auto emit() -> ct_check_value + requires stаtiс_аssert; +}; +template <> struct ct_check_t { + template constexpr static auto emit() -> ct_check_value { + return {}; + } +}; +template constexpr auto ct_check = ct_check_t{}; + +namespace detail { +template +constexpr auto static_format() + requires(... and cx_value) +{ + return ct_format(Args...); +} +} // namespace detail + +} // namespace v1 +} // namespace stdx + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define STATIC_ASSERT(cond, ...) \ + []() -> bool { \ + stdx::ct_check.template emit()>(); \ + return B; \ + }.template operator()() + +#endif diff --git a/include/stdx/utility.hpp b/include/stdx/utility.hpp index 1a18418..6c9be23 100644 --- a/include/stdx/utility.hpp +++ b/include/stdx/utility.hpp @@ -151,9 +151,12 @@ struct type_val { friend constexpr auto operator+(T &&t, U &&) -> T { return t; } - friend constexpr auto operator+(type_val const &f) -> type_val; + friend constexpr auto operator+(type_val const &f) -> type_val { return f; } // NOLINTNEXTLINE(google-explicit-constructor) - template constexpr operator T() const; + template constexpr operator T() const { + extern auto cxv_type_val_get_t(T *) -> T; + return cxv_type_val_get_t(nullptr); + } }; template constexpr auto is_type() -> std::false_type; @@ -166,6 +169,14 @@ template struct typer { template constexpr auto type_of() -> void; template constexpr auto type_of() -> typename typer::type; + +class cx_base { + struct unusable {}; + + public: + using cx_value_t [[maybe_unused]] = void; + constexpr auto operator()(unusable) const {} +}; } // namespace cxv_detail template @@ -192,31 +203,24 @@ constexpr auto is_aligned_with = [](auto v) -> bool { #ifndef CX_VALUE #define CX_VALUE(...) \ - [] { \ + []() constexpr { \ STDX_PRAGMA(diagnostic push) \ STDX_PRAGMA(diagnostic ignored "-Wold-style-cast") \ STDX_PRAGMA(diagnostic ignored "-Wunused-value") \ if constexpr (decltype(stdx::cxv_detail::is_type< \ stdx::cxv_detail::from_any( \ __VA_ARGS__)>())::value) { \ - [[maybe_unused]] struct { \ - constexpr auto operator()() const noexcept { \ - return stdx::type_identity< \ - decltype(stdx::cxv_detail::type_of< \ - stdx::cxv_detail::from_any( \ - __VA_ARGS__)>())>{}; \ - } \ - using cx_value_t [[maybe_unused]] = void; \ - } val; \ - return val; \ + return stdx::overload{stdx::cxv_detail::cx_base{}, [] { \ + return stdx::type_identity< \ + decltype(stdx::cxv_detail::type_of< \ + stdx::cxv_detail::from_any( \ + __VA_ARGS__)>())>{}; \ + }}; \ } else { \ - [[maybe_unused]] struct { \ - constexpr auto operator()() const { \ - return (__VA_ARGS__) + stdx::cxv_detail::type_val{}; \ - } \ - using cx_value_t [[maybe_unused]] = void; \ - } val; \ - return val; \ + return stdx::overload{stdx::cxv_detail::cx_base{}, [] { \ + return (__VA_ARGS__) + \ + stdx::cxv_detail::type_val{}; \ + }}; \ } \ STDX_PRAGMA(diagnostic pop) \ }() diff --git a/test/fail/CMakeLists.txt b/test/fail/CMakeLists.txt index aefbeea..4fcb2d2 100644 --- a/test/fail/CMakeLists.txt +++ b/test/fail/CMakeLists.txt @@ -27,6 +27,7 @@ add_fail_tests( function(add_formatted_error_tests) add_fail_tests(ct_check) add_fail_tests(static_assert) + add_fail_tests(static_assert_format) endfunction() if(${CMAKE_CXX_STANDARD} GREATER_EQUAL 20) diff --git a/test/fail/ct_check.cpp b/test/fail/ct_check.cpp index 21f8909..c0e511e 100644 --- a/test/fail/ct_check.cpp +++ b/test/fail/ct_check.cpp @@ -1,4 +1,4 @@ -#include +#include // EXPECT: 01234567890123456789012345678901234567890123456789 diff --git a/test/fail/static_assert.cpp b/test/fail/static_assert.cpp index 2465c8d..794d18e 100644 --- a/test/fail/static_assert.cpp +++ b/test/fail/static_assert.cpp @@ -1,4 +1,4 @@ -#include +#include // EXPECT: 01234567890123456789012345678901234567890123456789 diff --git a/test/fail/static_assert_format.cpp b/test/fail/static_assert_format.cpp new file mode 100644 index 0000000..162c054 --- /dev/null +++ b/test/fail/static_assert_format.cpp @@ -0,0 +1,14 @@ +#include +#include + +#include + +// EXPECT: hello world int 123 + +template constexpr auto f() { + using namespace std::string_view_literals; + STATIC_ASSERT(false, "hello {} {} {}", CX_VALUE("world"sv), CX_VALUE(T), + CX_VALUE(123)); +} + +auto main() -> int { f(); }