Last Update:
Improve Multiplatform Code With __has_include and Feature Test Macros
Table of Contents
Two weeks ago, I showed you a sample that can detect if a function has a given overload. The example revolved around std::from_chars
- low-level conversion routine for C++17. In the example, some “heavy” template patterns helped me to write the final code (most notably std::void_t
and if constexpr
). Maybe there are some other techniques we can use to check if a feature is available or not?
Today I’d like to have a look at __has_include
and discuss the upcoming feature test macros that we’ll have in C++20.
__has_include
For many years __has_include
was available as an extension in Clang. Now it’s in the Standard!
As the name suggests, it can help us checking if a given header exists.
For example, OpenGL headers under MacOS are located in OpenGL\
directory, while on other platforms they are in GL\
.
Usually, we can check for a platform macro and write the following code:
#ifdef __APPLE__
# include <OpenGL/gl.h>
# include <OpenGL/glu.h>
#else
# include <GL/gl.h>
# include <GL/glu.h>
#endif
With __has_include
the previous code can be rewritten into:
#if __has_include(<GL/gl.h>)
# include <GL/gl.h>
# include <GL/glu.h>
#else
# include <OpenGL/gl.h>
# include <OpenGL/glu.h>
#endif
Now, the code doesn’t depend on the platform name, which might be better in some cases.
What’s more, we can leverage it to test for a whole feature of C++. For example, GCC 7 supports many C++17 features, but not std::from_chars
, while GCC 9.1 is improved and contains that header.
We can write the following code:
#if defined __has_include
# if __has_include(<charconv>)
# define has_charconv 1
# include <charconv>
# endif
#endif
std::optional<int> ConvertToInt(const std::string& str) {
int value { };
#ifdef has_charconv
const auto last = str.data() + str.size();
const auto res = std::from_chars(str.data(), last, value);
if (res.ec == std::errc{} && res.ptr == last)
return value;
#else
// alternative implementation...
#endif
return std::nullopt;
}
In the above code, we declare has_charconv
based on the __has_include
condition. If the header is not there, we need to provide an alternative implementation for ConvertToInt
.
You can check this code against GCC 7.1 and GCC 9.1 and see the effect as GCC 7.1 doesn’t expose the charconv
header.
For example at @Wandbox
Another example is related to optional
. The paper that proposes __has_include
(P0061) shows the following example:
#if __has_include(<optional>)
# include <optional>
# define have_optional 1
#elif __has_include(<experimental/optional>)
# include <experimental/optional>
# define have_optional 1
# define experimental_optional 1
#else
# define have_optional 0
#endif
// later in code
#if have_optional == 1
#ifndef experimental_optional
std::optional<int> oint;
#else
std::experimental::optional<int> oint;
#endif
/// ...
Now, we check for optional
, and we can even try switching back to experimental/optional
.
__has_include
is available even without the C++17 flag switch, that’s why you can check for a feature also if you work in C++11, or C++14 “mode”.
Header Stubs
Thanks to comments at r/cpp (Thanks to Billy O’Neil) I realised that I skipped one important aspect: what if a compiler/library provides only header stubs? You might think that a feature is enabled, but the header is “empty”.
Let’s have a look at a <execution>
header - that should mean if parallel algorithms are available (in C++17).
If you compile with C++14 flag, then the header is “empty”:
// MSVC 2019:
// ...
// ...
#if _HAS_CXX17 // <<!!
#include <algorithm>
// ... the rest
#endif _HAS_CXX17 // <<!!
Similarly GCC and Clang also check if you compiler with the C++17 flag (or above).
If you compile with a wrong language flag, then the header will be present and __has_include
returns 1
, but still the feature is turned off.
Something Better?
__has_include
can check for a full header, and it’s convenient when a feature has a separate file (assuming it’s not a stub). But what if you want to check for some small feature that shares the same source file? Or when you ask for a general feature like if if constexpr
is available?
It appears we might get some help in C++20 :)
Feature Test Macros
In C++20 we’ll have standardised feature test macros that simplify checking C++ feature existence.
For example, you’ll be able to test for std::optional
through __cpp_lib_optional
or even if the compiler supports an attribute: __has_cpp_attribute
.
The code from the previous section about optional
can be simplified a bit as we don’t need to define have_optional
macros:
#if __has_include(<optional>)
# include <optional>
#else __has_include(<experimental/optional>)
# include <experimental/optional>
# define experimental_optional 1
#endif
// later:
#ifdef __cpp_lib_optional // <<
# ifndef experimental_optional
std::optional<int> oint;
# else
std::experimental::optional<int> oint;
#endif
GCC, Clang and Visual Studio exposes many of the macros already, even before C++20 is ready.
Before C++20 we can also look at boost.config
that already exposes lots of macros that defines if a compiler support given feature. For many compilers boost has to use complex checks, for example:
// BOOST_NO_CXX11_LAMBDAS
#if (BOOST_INTEL_CXX_VERSION >= 1200) && \
(!defined(BOOST_INTEL_GCC_VERSION) || \
(BOOST_INTEL_GCC_VERSION >= 40500)) && (!defined(_MSC_VER) || \
(_MSC_VER >= 1600))
# undef BOOST_NO_CXX11_LAMBDAS
#endif
But if all compilers support feature test macros, the you’ll be able to just check
#if __cpp_lambdas
//code
#endif
As you see that can significantly simplify the code for many libraries that works on many platforms and compilers!
Read more in Feature testing (C++20) - cppreference
Summary
With so many different platforms and compilers, it’s sometimes hard to check if you can use some feature or not. This is especially crucial if your code is built on many configurations and systems.
Fortunately, with C++17 (through __has_include
) and feature test macros in C++20, such tests should be much more straightforward.
Have you used __has_include
in your code? Did it simplify the check for some header or feature? Let us know in comments!
You can also watch Jason Turner’s episode about this feature: C++ Weekly - Ep 23 C++17’s __has_include. His example showed how to check if your code has POSIX support.
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here: