C++ Logo

sg10

Advanced search

[SG10] Checking __has_include on its own is not sufficient

From: Jonathan Wakely <cxx_at_[hidden]>
Date: Thu, 9 Feb 2017 22:59:44 +0000
It's become apparent that the feature-test recommendations for
features such as std::string_view, std::variant and std::optional are
not sufficient.

SD-6 says that to check for std::variant you should use
__has_include(<variant>).

This doesn't always work, for example:

#ifdef __has_include
#if __has_include(<variant>)
#include <variant>
std::variant<int> v;
#endif
#endif
int main(){ }

Compiling this program with g++ -std=c++14 (using libstdc++) gives:

In file included from /opt/wandbox/gcc-head/include/c++/7.0.1/variant:35:0,
                 from prog.cc:3:
/opt/wandbox/gcc-head/include/c++/7.0.1/bits/c++17_warning.h:32:2:
error: #error This file requires compiler and library support for the
ISO C++ 2017 standard. This support must be enabled with the
-std=c++17 or -std=gnu++17 compiler options.
 #error This file requires compiler and library support \
  ^~~~~
prog.cc:4:6: error: 'variant' in namespace 'std' does not name a template type
 std::variant<int> v;
      ^~~~~~~

Live demo: http://melpon.org/wandbox/permlink/qwYfRA1FPBsIsCXi

And with clang++ -std=c++14 (using libc++):

prog.cc:4:6: error: no type named 'variant' in namespace 'std'
std::variant<int> v;
~~~~~^

Live demo: http://melpon.org/wandbox/permlink/GTrG5HpX1lCT07Dx

The problem is that both standard libraries provide the <variant>
header, so __has_include(<variant>) evaluates to 1, but the contents
of the header can only be used with the compiler's C++17 support
enabled. Libstdc++ issues a fatal error telling the user what's wrong,
and libc++ just silently fails to define anything (the header is
effectively empty for C++14 mode) so using std::variant fails even
though the header was included without error.

To fix this, SD-6 could recommend that _cplusplus is also checked, i.e.

#ifdef __has_include
#if __has_include(<variant>) && __cplusplus > 201402L

This will work for GCC and Clang, but is sub-optimal, because libc++
defines lots of features in older standard modes e.g. this works
perfectly using libc++ with -std=c++14:

#ifdef __has_include
#if __has_include(<string_view>)
#include <string_view>
#endif
#endif
int main(){ std::string_view s; }

Live demo: http://melpon.org/wandbox/permlink/Zzfn2a8OGXecLCXc

So if the code was changed to check __cplusplus > 201402L it would
decide that string_view isn't available (and presumably try to make do
with experimental::string_view or some homemade replacement). That
would work, but the feature-test isn't doing a very good job if it
fails to correctly determine that the feature actually is available.

A sufficiently motivated user could do:

#ifdef __has_include
# if __has_include(<string_view>)
# include <ciso646>
# if _LIBCPP_VERSION
# include <string_view> // always safe for libc++
# elif __GLIBCXX__
# if __cplusplus > 201402L
# include <string_view> // only safe in C++17 for libstdc++
# endif
# elif ... // other implementations
...
# endif
# endif
#endif

But this requires hardcoding knowledge about implementation details
into the feature-tests, which is what SD-6 is supposed to prevent.

To solve this I propose that all the feature-tests that rely solely on
__has_include should additionally rely on a new __cpp_lib_xxx macro.
This applies to optional, any, string_view, memory_resource, variant,
execution, filesystem, and shared_mutex, and to several
<experimental/*> features defined in a TS.

To test for one of those features you would need to check if the
header is present using __has_include, and then try including it and
check if the macro is defined. So assuming we added
__cpp_lib_optional, the example in 3.2.2 would become

#ifdef __has_include
# if __has_include(<optional>)
# include <optional>
# if __cpp_lib_optional >= nnnnnn
# define have_optional 1
# endif
# elif __has_include(<experimental/optional>)
# include <experimental/optional>
# #if __cpp_lib_experimental_optional >= nnnnnn
# define have_optional 1
# define experimental_optional
# endif
#endif

#ifndef have_optional
# define have_optional 0
#endif

This would still give a fatal error with libstdc++ today if compiled
in C++14 mode (which is already the case for the example in SD-6) but
I could change the libstdc++ headers so they don't use #error when
included incorrectly. Instead the header could always be included, but
it would not define anything (including the __cpp_lib_optional macro)
when std::optional isn't declared.

Libc++ would require only minimal changes, simply defining the
__cpp_lib_optional macro when the header is included "correctly".

Received on 2017-02-10 00:00:07