[SG10] Checking __has_include on its own is not sufficient

Jonathan Wakely cxx at kayari.org
Thu Feb 9 23:59:44 CET 2017


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".


More information about the Features mailing list