Date: Sat, 31 May 2014 19:56:03 +0200
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.
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.
Received on 2014-05-31 19:56:09