It is really unfortunate that Clang decided to put users in this position. Since the intent of
#if __has_cpp_attribute(attr)
Was that if that returns non-zero, that you can... actually use the attribute. Warning after a check means that you suddenly need far more information to do a proper check.
Now, this answer suggests writing:
#if __has_cpp_attribute(assume) && __cplusplus >= __has_cpp_attribute(assume)
This is actually incorrect. The problem here is that the value that __has_cpp_attribute(attr) returns isn't fixed. It might change. For instance, [[nodiscard]] was an attribute that was added in C++17, initially with the value 201603. Then, during C++20, you were allowed to add a reason to it, so it now uses the value 201907.
Imagine I applied the above check to [[nodiscard]]:
#if __has_cpp_attribute(nodiscard) && __cplusplus >= __has_cpp_attribute(nodiscard)
# define NODISCARD [[nodiscard]]
#else
# define NODISCARD
#endif
If I'm compiling with clang 9 in c++17 mode, I would get [[nodiscard]]. But with clang 10 in c++17 mode, I would suddenly get nothing — because clang 10 implemented the added [[nodiscard]] functionality, so now our check fails. And we don't get any [[nodiscard]] at all! For example:
#if __has_cpp_attribute(nodiscard) && __cplusplus >= __has_cpp_attribute(nodiscard)
# define NODISCARD [[nodiscard]]
#else
# define NODISCARD
#endif
NODISCARD int f() { return 0; }
int main() {
f(); // warns with clang 9, not with clang 10+
}
Instead, you just have to know.
That is, the specific check you need to write for [[nodiscard]] is this:
#if __has_cpp_attribute(nodiscard) && __cplusplus >= 201603
And likewise the specific check you have to write for assume is
#if __has_cpp_attribute(assume) && __cplusplus >= 202207
Thankfully, you can at least easily find these values in the feature-test document (also via the link wg21.link/sd6). But it would have been nice to not have to do this.
#if __has_cpp_attribute(assume) >= 202207L && __cplusplus >= __has_cpp_attribute(assume)