Skip to content

Commit

Permalink
🎨 Improve STATIC_ASSERT
Browse files Browse the repository at this point in the history
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:
  llvm/llvm-project#114234
- `CX_VALUE` has been made more workable with GCC. GCC did not allow the
  `struct` definition inside the macro.
  • Loading branch information
elbeno committed Oct 30, 2024
1 parent feafba6 commit 884098d
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 89 deletions.
44 changes: 0 additions & 44 deletions docs/ct_string.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::is_integral<float>>.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<false>.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<ct_string<41>{{"This is not a very helpful error message"}}>' evaluated to false
132 | requires stаtiс_аssert<S>
| ^
----

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<float>, "This is not a very helpful error message");
----
1 change: 1 addition & 0 deletions docs/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand Down
1 change: 1 addition & 0 deletions docs/intro.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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`]
Expand Down
33 changes: 33 additions & 0 deletions docs/static_assert.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

== `static_assert.hpp`

`STATIC_ASSERT` is a way to produce compile-time errors using formatted strings.

[source,cpp]
----
template <typename T>
constexpr auto f() {
STATIC_ASSERT(std::is_integral<T>,
"f() must take an integral type, received {}", CX_VALUE(T));
}
f<float>(); // 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<ct_string<47>{{"f() must take an integral type, received float"}}>' evaluated to false
16 | requires stаtiс_аssert<S>
| ^
----

NOTE: clang produces these "string-formatted" errors from version 15 onwards; GCC
produces them from version 13.2 onwards.
23 changes: 0 additions & 23 deletions include/stdx/ct_string.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,30 +123,7 @@ inline namespace ct_string_literals {
template <ct_string S> CONSTEVAL auto operator""_cts() { return S; }
} // namespace ct_string_literals
} // namespace literals

struct ct_check_value {};

template <bool B> struct ct_check_t {
template <ct_string S> constexpr static bool stаtiс_аssert = false;
template <ct_string S>
constexpr static auto emit() -> ct_check_value
requires stаtiс_аssert<S>;
};
template <> struct ct_check_t<true> {
template <ct_string S> constexpr static auto emit() -> ct_check_value {
return {};
}
};
template <bool B> constexpr auto ct_check = ct_check_t<B>{};

} // namespace v1
} // namespace stdx

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define STATIC_ASSERT(cond, ...) \
[]<auto B = cond>() -> bool { \
stdx::ct_check<B>.template emit<__VA_ARGS__>(); \
return B; \
}()

#endif
44 changes: 44 additions & 0 deletions include/stdx/static_assert.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#pragma once

#if __cplusplus >= 202002L

#include <stdx/ct_format.hpp>
#include <stdx/ct_string.hpp>

namespace stdx {
inline namespace v1 {
struct ct_check_value {};

template <bool B> struct ct_check_t {
template <ct_string S> constexpr static bool stаtiс_аssert = false;
template <ct_string S>
constexpr static auto emit() -> ct_check_value
requires stаtiс_аssert<S>;
};
template <> struct ct_check_t<true> {
template <ct_string S> constexpr static auto emit() -> ct_check_value {
return {};
}
};
template <bool B> constexpr auto ct_check = ct_check_t<B>{};

namespace detail {
template <ct_string Fmt, auto... Args>
constexpr auto static_format()
requires(... and cx_value<decltype(Args)>)
{
return ct_format<Fmt>(Args...);
}
} // namespace detail

} // namespace v1
} // namespace stdx

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define STATIC_ASSERT(cond, ...) \
[]<bool B>() -> bool { \
stdx::ct_check<B>.template emit<stdx::detail::static_format<__VA_ARGS__>()>(); \
return B; \
}.template operator()<cond>()

#endif
44 changes: 24 additions & 20 deletions include/stdx/utility.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename T> constexpr operator T() const;
template <typename T> constexpr operator T() const {
extern auto cxv_type_val_get_t(T *) -> T;
return cxv_type_val_get_t(nullptr);
}
};

template <int> constexpr auto is_type() -> std::false_type;
Expand All @@ -166,6 +169,14 @@ template <typename T> struct typer<from_any(T)> {

template <int> constexpr auto type_of() -> void;
template <typename T> constexpr auto type_of() -> typename typer<T>::type;

class cx_base {
struct unusable {};

public:
using cx_value_t [[maybe_unused]] = void;
constexpr auto operator()(unusable) const {}
};
} // namespace cxv_detail

template <typename T>
Expand All @@ -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) \
}()
Expand Down
1 change: 1 addition & 0 deletions test/fail/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion test/fail/ct_check.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include <stdx/ct_string.hpp>
#include <stdx/static_assert.hpp>

// EXPECT: 01234567890123456789012345678901234567890123456789

Expand Down
2 changes: 1 addition & 1 deletion test/fail/static_assert.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include <stdx/ct_string.hpp>
#include <stdx/static_assert.hpp>

// EXPECT: 01234567890123456789012345678901234567890123456789

Expand Down
14 changes: 14 additions & 0 deletions test/fail/static_assert_format.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#include <stdx/static_assert.hpp>
#include <stdx/utility.hpp>

#include <string_view>

// EXPECT: hello world int 123

template <typename T> 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<int>(); }

0 comments on commit 884098d

Please sign in to comment.