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 afunction has a given overload. The example revolved aroundstd::from_chars– low-level conversion routine for C++17. In the example, some “heavy” template patterns helped me to write the final code (most notablystd::void_tandif 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_includeand discuss the upcoming feature test macros that we’ll have in C++20.

__has_include

For many years__has_includewas 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 inOpenGLdirectory, while on other platforms they are inGL.

Usually, we can check for a platform macro and write the following code:

#ifdef__APPLE__
#   include
#   include
#else
#   include
#   include
#endif

With__has_includethe previous code can be rewritten into:

#if __has_include()
#   include
#   include
#else
#   include
#   include
#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 notstd::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()
#        define has_charconv 1
#        include
#    endif
#endif

std::optionalint>ConvertToInt(conststd::string& str) {
   intvalue { };
   #ifdef has_charconv
       constautolast=str.data() + str.size();
       constautores=std::from_chars(str.data(), last, value);
       if(res.ec==std::errc{} && res.ptr==last)
           returnvalue;
   #else
        
   #endif

   returnstd::nullopt;
}

In the above code, we declarehas_charconvbased on the__has_includecondition. If the header is not there, we need to provide an alternative implementation forConvertToInt.

You can check this code against GCC 7.1 and GCC 9.1 and see the effect as GCC 7.1 doesn’t expose thecharconvheader.

For example at@Wandbox

Another example is related tooptional. The paper that proposes__has_include(P0061) shows the following example:

#if __has_include()
#  include
#  define have_optional 1
#elif__has_include(experimental/optional>)
#  include
#  define have_optional 1
#  define experimental_optional 1
#else
#  define have_optional 0
#endif

// later in code
#if have_optional==1
#ifndefexperimental_optional 
std::optionaloint;
#else
std::experimental::optionaloint;
#endif
/// ...

Now, we check foroptional, and we can even try switching back toexperimental/optional.

__has_includeis 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”.

Thanks to comments at r/cpp (Thanksto 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 aheader – that should mean if parallel algorithms are available (in C++17).

If you compile with C++14 flag, then the header is “empty”:





#if _HAS_CXX17     
#include

#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_includereturns1, but still the feature is turned off.

Something Better?

__has_includecan 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 ifif constexpris 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 forstd::optionalthrough__cpp_lib_optionalor even if the compiler supports an attribute:__has_cpp_attribute.

The code from the previous section aboutoptionalcan be simplified a bit as we don’t need to definehave_optionalmacros:

#if __has_include()
#  include
#else__has_include(experimental/optional>)
#  include
#  define experimental_optional 1
#endif

// later:
#ifdef__cpp_lib_optional  //
#  ifndef experimental_optional
   std::optionaloint;
#  else
   std::experimental::optionaloint;
#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 atboost.configthat 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 inFeature 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_includein 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.

Read More

LEAVE A REPLY

Please enter your comment!
Please enter your name here