Everyone who has answered that the right thing do it is to use feature detection macros like __has_feature
, __has_builtin
, etc., is right. If that's possible for your use case, that's what you should do.
That said, there are times when clang doesn't expose anything specific to what you're trying to check. For example, there is no way to tell if a particular SSE/AVX or NEON function is available (and yes, new ones are added from time to time; the instructions the CPU supports are fixed, but sometimes new functions using existing instructions are added to plug a hole in the API). Or maybe there was a bug and clang was generating incorrect machine code. We actually run into these types of issues a fairly often in the SIMDe project.
Unfortunately you can't rely on __clang_major__
/__clang_minor__
/__clang_patchlevel__
. Vendors like Apple take clang and repackage it as their own compiler with their own version numbers, and when they do they generally also change the __clang_*__
versions to match their compiler version, not upstream clang's. For example, Apple clang 4.0 is really a repackaged clang 3.1, but they set __clang_major__
to 4 and __clang_minor__
to 0.
The best solution I've found is to use the feature detection code to detect completely unrelated features which just so happen to be added in the same version as what you really want to detect. I've been doing this for a while, but earlier today I finally put together a header to keep all that logic in one place. It's part of SIMDe, but has no dependencies whatsoever and is public domain (CC0). I'll probably forget to update this answer with any improvements in the future, so please check the repo for the current version, but here is what it looks like right now:
#if !defined(SIMDE_DETECT_CLANG_H)
#define SIMDE_DETECT_CLANG_H 1
#if defined(__clang__) && !defined(SIMDE_DETECT_CLANG_VERSION)
# if __has_attribute(unsafe_buffer_usage)
# define SIMDE_DETECT_CLANG_VERSION 170000
# elif __has_attribute(nouwtable)
# define SIMDE_DETECT_CLANG_VERSION 160000
# elif __has_warning("-Warray-parameter")
# define SIMDE_DETECT_CLANG_VERSION 150000
# elif __has_warning("-Wbitwise-instead-of-logical")
# define SIMDE_DETECT_CLANG_VERSION 140000
# elif __has_warning("-Waix-compat")
# define SIMDE_DETECT_CLANG_VERSION 130000
# elif __has_warning("-Wformat-insufficient-args")
# define SIMDE_DETECT_CLANG_VERSION 120000
# elif __has_warning("-Wimplicit-const-int-float-conversion")
# define SIMDE_DETECT_CLANG_VERSION 110000
# elif __has_warning("-Wmisleading-indentation")
# define SIMDE_DETECT_CLANG_VERSION 100000
# elif defined(__FILE_NAME__)
# define SIMDE_DETECT_CLANG_VERSION 90000
# elif __has_warning("-Wextra-semi-stmt") || __has_builtin(__builtin_rotateleft32)
# define SIMDE_DETECT_CLANG_VERSION 80000
# elif __has_warning("-Wc++98-compat-extra-semi")
# define SIMDE_DETECT_CLANG_VERSION 70000
# elif __has_warning("-Wpragma-pack")
# define SIMDE_DETECT_CLANG_VERSION 60000
# elif __has_warning("-Wasm-ignored-qualifier")
# define SIMDE_DETECT_CLANG_VERSION 50000
# elif __has_attribute(diagnose_if)
# define SIMDE_DETECT_CLANG_VERSION 40000
# elif __has_warning("-Wcomma")
# define SIMDE_DETECT_CLANG_VERSION 30900
# elif __has_warning("-Wmicrosoft")
# define SIMDE_DETECT_CLANG_VERSION 30800
# else
# define SIMDE_DETECT_CLANG_VERSION 1
# endif
#endif /* defined(__clang__) && !defined(SIMDE_DETECT_CLANG_VERSION) */
#if defined(SIMDE_DETECT_CLANG_VERSION)
# define SIMDE_DETECT_CLANG_VERSION_CHECK(major, minor, revision) (SIMDE_DETECT_CLANG_VERSION >= ((major * 10000) + (minor * 1000) + (revision)))
# define SIMDE_DETECT_CLANG_VERSION_NOT(major, minor, revision) SIMDE_DETECT_CLANG_VERSION_CHECK(major, minor, revision)
#else
# define SIMDE_DETECT_CLANG_VERSION_CHECK(major, minor, revision) (0)
# define SIMDE_DETECT_CLANG_VERSION_NOT(major, minor, revision) (1)
#endif
#endif /* !defined(SIMDE_DETECT_CLANG_H) */