C++ Logo

SG10

Advanced search

Subject: [SG10] A view from CMake
From: Stephen Kelly (steveire_at_[hidden])
Date: 2014-05-31 12:56:03


Hi,

Over the last few months I implemented 'compile feature' support in the
CMake buildsystem tool.

This is somewhat related to the work of SG10, in that the macros
recommended here can be used by this CMake functionality in the future.
These use cases may not be obvious, so I thought I'd list them here for
information and feedback and relate some of the experience.

The intention is to support use-cases such as

1) This code requires constexpr. Fail early with an informative error
message at cmake time (before compilation) if the compiler in use does
not have that feature. Don't rely on translation failing, because that
might happen many hours after the start of the build if the buildsystem
is large (which is very common).

2) Generate a portable header containing #defines for features so that
the code can use the features optionally.

3) Use these include directories instead of those include directories if
variadic templates are supported by the compiler in use. Use this
library (and its headers) instead of that library if variadic templates
are supported by the compiler in use.

These use cases are described in greater detail in the documentation:

 http://www.cmake.org/cmake/help/git-master/manual/cmake-compile-features.7.html

 http://www.cmake.org/cmake/help/git-master/module/WriteCompilerDetectionHeader.html

The features known to CMake which can be listed as requirements etc are
currently a list of C++11 and C++14 features listed here:

 http://clang.llvm.org/cxx_status.html

 http://www.cmake.org/cmake/help/git-master/prop_gbl/CMAKE_CXX_KNOWN_FEATURES.html

The names of features deliberately follow names used by Clang features,
with some exceptions and extensions.

Early when it is executed, and after determining the compiler in use,
CMake generates a file with content containing preprocessor if
conditions (similar to the status quo described in SD6), compiles it
(possibly with -std=c++11 etc flags, as needed), and then extracts
information about which features were recorded as available during
compilation. The mapping from compiler version macros (and __cplusplus
macro) to supported features are stored in files shipped with CMake.

GNU:

 http://cmake.org/gitweb?p=cmake.git;a=blob;f=Modules/Compiler/GNU-CXX-FeatureTests.cmake;h=9c98e447

For Clang, __has_feature is used where possible:

 http://cmake.org/gitweb?p=cmake.git;a=blob;f=Modules/Compiler/Clang-CXX-FeatureTests.cmake;h=4c532fb0

Even though Clang supports __has_feature, we still use a version check
on Clang because Clang prior to 3.4 has not been tested by me for this
CMake functionality and is not supported by CMake. If historical
versions of Clang are tested in the future, that version test may be
removed entirely, leaving only the __has_feature check.

Other compilers are not yet supported, but are designed to be supported
in this concept. It is designed to be future-proof as the features
specified by the standard change, hiding the 'language version' from the
user, and letting them choose required features instead:

 http://www.kdab.com/modern-cmake-with-qt-and-boost/#compile-feature-specification

Obviously, the work of SG10 may simplify the implementation of this
CMake feature, which is where I become interested. The existence of the
uniform recommended feature tests is useful not only to discrete
compilations, which is what SD6 seems to focus on, but can be extracted
by the buildsystem to make the use-cases I listed above possible.

----
The __has_include() functionality is not of interest to this CMake
feature, because CMake has its own facilities for determining existence
of include directories before compilation. Of course, programmers may be
able to use __has_include() instead of those CMake functionalities in
the future which would be fine and good.
I suppose the CMake functionality could be implemented on top of the
__has_include feature as described too:
 try_compile(
  "#if !__has_include(${header_name})
   #error File not found
   #endif"
 )
It appears that if following SD6, code such as
 static_assert(__has_include(${header_name}), "header not found")
is not allowed, right?
----
Regarding the note here:
 https://isocpp.org/files/papers/n4030.htm#detail.cpp14.n3323
Clang fails to compile code relying on the described contextual
conversions unless invoked with -std=c++1y.
CMake has an explicit feature listing for that to allow the 'fail early'
use-case where that feature is relied upon. Other features not listed in
SD6 may be used explicitly in the CMake functionality for the same reason.
----
The names of some features known to CMake are not the same as those used
by Clang/SD6. For example:
1) I don't know what the 'nsdmi' part of __cpp_aggregate_nsdmi means, so
I named that feature 'cxx_aggregate_default_initializers' as it is for
mortal consumption.
2) cxx_init_captures seemed too generically named, so I added a 'lambda'
to the name: cxx_lambda_init_captures.
----
The CMake compile feature concept applies only to language features, not
to library features. It may be extended to cover library features in the
future however, and then it would be a disadvantage that SD6 recommends
that the feature test macro definitions be spread over multiple header
files.
In order to satisfy the 'fail early' use-case, CMake would then have to
do a compilation of a file which contains something like:
 #include <functional>
 #include <utility>
 #include <map>
 #include <iterator>
 // ... etc, other headers
 #if __cpp_null_iterators
 // ...
 #endif
 #if __cpp_lib_result_of_sfinae
 // ...
 #endif
 // ... etc, other features
and record the results in a way similar to how it is currently done for
the language features.
Because of the need to include so many header files, using the
recommended macros may be prohibitively expensive.
It would be better from that point of view to have all of the __cpp_lib
macros defined in a single header file. That would also enable the user
to not include files they don't need to use
 #include <ciso686>
 #if __cpp_lib_result_of_sfinae
 #  include <functional> // for result_of
 # else
 #  include <utility> // for declval
 #endif
template<typename A>
#if __cpp_lib_result_of_sfinae
  typename std::result_of<inc(A)>::type
#else
  decltype(std::declval<inc>()(std::declval<A>()))
#endif
try_inc(A a);
rather than including something and then determining whether it is
useful and should have been included. This would be similar to the
status-quo of version testing with libc++:
 http://thread.gmane.org/gmane.comp.compilers.clang.devel/22916/focus=22917
though as far as I know, something similar is not possible with
libstdc++ because there is no useful version macro for the library:
 http://stackoverflow.com/a/11925468/2428389
Although CMake is not planning on recording library features yet anyway,
Boost.Config does record some library features, and may have to include
many headers in order to access the macros, which might be undesirable.
Consider recommending definition of the __cpp_lib macros in a single
file instead of in multiple files.
----
There are problems with bugs in implementations which I don't think I
have a good solution for. Implementations could document support for a
feature, but forget to enable the macro for it. For example, Clang 3.4
documents support for decltype(auto) and generic lambdas, but
__has_feature returns the wrong result for those features before Clang 3.5:
 http://llvm.org/bugs/show_bug.cgi?id=19698
 http://llvm.org/bugs/show_bug.cgi?id=19674
So, CMake must use a version check instead
There may be less chance of such bugs if the macro to define is
documented in the same paper as the feature, so that's good.
----
There is also the problem that implementations may document availability
of a feature and define the correct macro for it, but the feature may be
broken in the implementation such that it must be treated as unavailable
in a widely used library:
 http://thread.gmane.org/gmane.comp.lib.boost.devel/244986/focus=245333
There are other examples in Qt which considers
cxx_uniform_initialization to be unavailable in MSVC because of
http://connect.microsoft.com/VisualStudio/feedback/details/802058/c-11-unified-initialization-fails-with-c-style-arrays
So, user code might still have to contain compiler version checks
 #if __cpp_variadic_templates
 #  if !defined(_MSC_VER) || _MSC_VER >= 1900
 #    define BOOST_VARIADIC_TEMPLATES
 #  endif
 #endif
I do think the recommended feature tests improve things, but for
completeness I wanted to list problems I've encountered while
researching and implementing this feature in CMake.
----
I hope some of this information and feedback is useful in some way. I
welcome any feedback on what I presented too.
Thanks,
Steve.

SG10 list run by sg10-owner@lists.isocpp.org