C++17 In Detail

22 July 2019

Improve Multiplatform Code With __has_include and Feature Test Macros

Multiplatform code and __has_include, C++17

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.

C++17 In Detail
© 2017, Bartlomiej Filipek, Blogger platform
Disclaimer: Any opinions expressed herein are in no way representative of those of my employers. All data and information provided on this site is for informational purposes only. I try to write complete and accurate articles, but the web-site will not be liable for any errors, omissions, or delays in this information or any losses, injuries, or damages arising from its display or use.
This site contains ads or referral links, which provide me with a commission. Thank you for your understanding.