CMake module to activate certain C++ standard, feature checks and appropriate automated workarounds - basically an improved version of cmake-compile-features
CMake C++
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.

README.md

Compatibility

Build Status

This library provides an advanced target_compile_features() and write_compiler_detection_header(). The problem with those is that they are controlled via a CMake internal database, which has to be kept manually up-to-date. This version uses check_cxx_source_compiles() instead and is such automatically up-to-date - a compiler supports a feature, if the test code compiles.

Based on the test results, a header file is generated that stores the result and often workaround code, for example a CONSTEXPR macro that is constexpr, if supported, and const otherwise, to use the features anyway.

It also provides the automatic standard deduction from target_compile_features() to activate the right standard.

Example

If you only want the C++ standard activation, simply include() comp_base.cmake and replace target_compile_features() by comp_compile_features().

This is a CMakeLists.txt for a more advanced usage:

# suppose we have a target 'tgt'
include(your/dir/to/comp_base.cmake) # only file you need to download, rest is taken as needed

# we want constexpr, noexcept, std::max_align_t and rtti_support
# instead of cpp11_lang/constexpr and cpp11_lang/noexcept, you could also write cxx_constexpr/cxx_noexcept
comp_target_features(tgt PUBLIC cpp11_lang/constexpr cpp11_lang/noexcept cpp11_lib/max_align_t env/rtti_support)

For convenience we include all generated files in a header named config.hpp. The filenames are made available through macros.

#ifndef CONFIG_HPP
#define CONFIG_HPP

#include COMP_CONSTEXPR_HEADER
#include COMP_NOEXCEPT_HEADER
#include COMP_MAX_ALIGN_T_HEADER
#include COMP_RTTI_SUPPORT_HEADER

#endif

And then we can use it just by including config.hpp in our code:

#include "config.hpp"

// use a workaround macro
COMP_CONSTEXPR int i = 0;

void func() COMP_NOEXCEPT
{
    // use a workaround typedef
    comp::max_align_t foo;

    // or conditional compilation
#if COMP_HAS_RTTI_SUPPORT
    do_sth();
#endif
}

If you don't care about the workarounds, but just want a specific standard, simply call:

comp_target_features(tgt PUBLIC CPP11) # or CPP14 or CPP17

This will only activate C++11/14/17 without doing anything else.

Note: The standard activation is always PRIVATE to allow users of a library to have a different (higher) standard than the library.

Usage

The only file needed is comp_base.cmake. You can either manually download it, use CMakes file(DOWNLOAD facility, or use git submodules. The branch git-submodule only contains comp_base.cmake and is thus perfect for this purpose. Run git submodule add -b "git-submodule" https://github.com/foonathan/compatibility.git to initialize it and fetch the file. Then you only need to run git submodule update --remote to update it to the newest version.

include() it in your CMakeLists.txt now. First it generates CMake options - COMP_CPP11_FLAG, COMP_CPP14_FLAG and COMP_CPP17_FLAG - storing the calculated compiler flag for a given standard, useful if you want to override it, if it can't find one for your compiler. It also provides the following function:

comp_target_features(<target> <PRIVATE|PUBLIC|INTERFACE> <features...>
                     [NOPREFIX | PREFIX <prefix] [NAMESPACE <namespace>]
                     [CMAKE_PATH <path>] [INCLUDE_PATH <include_path>]
                     [NOFLAGS | CPP11 | CPP14 | CPP17] 
                     [LOG <DEBUG | INFO | QUIET | SILENT | ALL>]
                     [NO_HEADER_MACROS] [SINGLE_HEADER <name>])

Ignoring all the other options, it is like target_compile_features(). It takes a list of features to activate for a certain target. A features is a file in this repository without the .cmake extension, e.g. cpp11_lang for all C++11 language features, or cpp14_lang/deprecated for the C++14 deprecated features.

A feature file with name dir/xxx.cmake belonging to a feature dir/xxx consists of the following, where dir is the category of the feature, xxx the feature name in lowercase, XXX the feature name in upper case <PREFIX> the prefix as given to the function, <prefix> the prefix in lowercase and without a trailing underscore:

  • a test testing whether or not the compiler supports this feature

  • a CMake option with name COMP_HAS_XXX to override its result, useful if you want to act like it doesn't support a feature, or if the test is poorly written (please contact me in this case!)

  • a header named <prefix>/xxx.hpp. The header contains at least a macro <PREFIX>HAS_XXX with the same value as the CMake option and often workaround macros or functions that can be used instead of the feature. The workaround uses either the feature, if it is available, or own code. This allows using many new features already, without support. If the compiler gets support, you will be automatically using the native feature or the standard library implementation.

  • a globally available macro named <PREFIX>_XXX_HEADER to include the header file.

To use the generated header files, simply write #include <PREFIX>_XXX_HEADER inside your code, the macro is made available automatically (you could also #incluce the file directly but this is not recommended).

What comp_target_features function actually does is the following:

  • For each feature, it downloads the latest version of the test file from Github, if it doesn't exist yet. The remote url can be set via the option COMP_REMOTE_URL.

  • For each feature, it calls include(feature.cmake) after some set up. This runs the test and generates the header file.

  • It calls target_include_directories to allow including the generated header files and target_compile_definitions for the file macro. The INTERFACE/PUBLIC/PRIVATE specifier are only used in these calls.

  • Activates the right C++ standard. E.g., if a feature requires C++11, it will be activated if it is available. If it is not, it will only activate the C++ standard required by the workaround code. This activation is always PRIVATE. The flag is exposed through the CACHE variable ${target}_COMP_COMPILE_OPTIONS.

The behavior can be customized with the other options:

  • NOPREFIX/PREFIX: The prefix of any generated macros or none if NOPREFIX is set. Default is COMP_.

  • NAMESPACE: The namespace name of any generated code, default is comp. A given prefix must always use the same namespace name!

  • CMAKE_PATH/INCLUDE_PATH: The download destination for the CMake files/the destination of the generated headers, default is ${CMAKE_BINARY_DIR}/comp.downloaded for cmake and ${CMAKE_BINARY_DIR}/comp.generated for the headers. INCLUDE_PATH is also given to target_include_directories(), but note that the generated headers are in a subfolder <prefix> (this cannot be changed).

  • NOFLAGS/CPP11/CPP14/CPP17: Override for the standard detection, if you want to have a newer standard than deduced from the features, or a lower (not recommended). They have priority over the deduction, C++17 over C++14 over C++11. Specify NOFLAGS if you do not want to have any compiler flags set. The latter is useful for INTERFACE libraries which are only there to run the tests and generate the options and headers.

  • LOG: Turn down or up the logging for features. There is no default value, but it is fairly verbose by default.

    • Possible options: DEBUG, INFO, QUIET, SILENT, ALL
    • Additionally, setting COMP_LOG_LEVEL at the global level behaves the same way, except any option passed here will override for the current run
  • NO_HEADER_MACROS: Disables the definition of the macros that contain the header file location.

  • SINGLE_HEADER: Generate a single header file with that name that will include all generated headers.

Feature Reference

A feature named dir/xxx is tested in xxx.cmake, defines an override CMake option COMP_HAS_XXX and a macro <PREFIX>HAS_XXX in a file named <prefix>/xxx.hpp (where prefix is <PREFIX> in lowercase without a trailing underscore), filename also made available over the global macro <PREFIX>XXX_HEADER.

There are also alternative names for the CMake target_compile_features() and SD-6 Feature Test Recommondations that are automatically translated. Where appropriate, it will also generate the SD-6 feature macro as specified. This will override the existing value if the new one is greater or the macro COMP_OVERRIDE_SD6 is defined. If a feature is not supported, it will not change or define anything.

For some features, macros are generated that can be used instead (i.e. for noexcept), they have the form <PREFIX>XXX. Those macros often use compiler extensions. If there is none (or a lacking implementation...), an error message will be emmitted. To prevent this, simply define the macro as no-op or as you want prior to including the file.

There are often workaround functions for library features. Those are defined in a namespace and either use the own implementation or the standard library implementation, if it is available.

Prefix and namespace name can be controlled via parameters, see above. A given prefix must always use the same namespace name on each call.

This library currently tests for the following features. The code below assumes no prefix and a namespace name of comp.

*A feature will only be included (by me) if it is not a pure syntactic feature (like auto or lambdas which can be avoided) but if there is either sensible workaround code, e.g. through to compiler extensions or through reimplementing (small!) standard library functionality, or there can be conditional compilation based on the existense, e.g. optional literal definitions or move constructors.

Note that I'm open to PRs of any kind :)*

C++11 language features

These features are all in the subdirectory cpp11_lang.

feature name alternative name example workaround, if any
alias_template cxx_alias_templas template <typename T> using my_map = std::map<int, T>; no workaround
alignas cxx_alignas alignas(int) char c ALIGNAS(x), fallback to compiler extension, if available
alignof cxx_alignof alignof(int) ALIGNOF(x), fallback to compiler extension, if available
auto_type cxx_auto_type auto var = 0 no workaround
constexpr cxx_constexpr constexpr int foo() CONSTEXPR, fallback to const; CONSTEXPR_FNC, fallback to inline
decltype cxx_decltype decltype(a) DECLTYPE(x), fallback to typeof extension, if available
default_function_template_args cxx_default_function_template_args template <typename T = int> void foo(); no workaround
delete_fnc cxx_deleted_functions void foo() = delete; no workaround
enum_class cxx_strong_enum enum class foo {} no workaround
explicit_conversion_op cxx_explicit_conversion explicit operator bool() no workaround
final cxx_final void bar() final; FINAL macro, workaround expands to nothing
inline_namespace none inline namespace foo {...} no workaround
lambdas cxx_lambdas [](){} no workaround
literal_op cxx_user_literals operator"" no workaround
noexcept cxx_noexcept void foo() noexcept; NOEXCEPT, fallback to nothing; NOEXCEPT_IF(x), fallback to nothing; NOEXCEPT_OP(x), fallback to false
noreturn none [[noreturn]] void foo(); NORETURN, fallback to compiler extension, if available
nullptr cxx_nullptr void* ptr = nullptr; NULLPTR, fallback to null pointer idiom; also comp::nullptr_t
override cxx_override void bar() override; OVERRIDE, fallback to nothing
parameter_pack cxx_parameter_pack template <typename ... T> void f(T... ts); no workaround
range_for cxx_range_for for (auto var : container) no workaround
right_angle_brackets cxx_right_angle_brackets std::vector<std::vector<int>> no workaround
rvalue_ref cxx_rvalue_references int&& a = 4; no workaround
static_assert cxx_static_assert static_assert(std::is_integral<T>::value, ""); STATIC_ASSERT(Expr, Msg), fallback to simple undefined struct technique
thread_local cxx_thread_local thread_local int i; THREAD_LOCAL, fallback to __thread extension or similar, if available - does not call constructors or destructors!

Note: In general, it assumes proper C++11 support. The workarounds defined in this library rely on all common C++ features that can not be easily avoided except those listed here with a proper fallback (like noexcept, constexpr, ...).

Get them all by specifying cpp11_lang.

C++11 library features

These features are all in the subdirectory cpp11_lib.

feature name example workaround, if any
get_new_handler std::get_new_handler() comp::get_new_handler(), fallback to old std::set_new_handler(nullptr) technique - not thread safe
get_terminate std::get_terminate() comp::get_terminate(), same as above
is_trivially std::is_trivially_XXX<T> comp::is_trivially_XXX, workaround uses std::is_XXX combined with std::is_trivial
max_align_t std::max_align_t comp::max_align_t, fallback to ::max_align_t or a struct with a long double and long long
mutex std::mutex/std::lock_guard/std::unique_lock no workaround
to_string std::to_string(54) comp::to_string(), fallback to std::sprintf()

Note: It only checks for minor features where an easy workaround implementation is feasible in the scope of this library.

Get them all by specifying cpp11_lib.

C++14 language features [complete]

These features are all in the subdirectory cpp14_lang.

paper feature name alternative name example workaround, if any
N3760 deprecated cxx_attribute_deprecated [[deprecated]] int foo(); DEPRECATED and DEPRECATED(Msg), fallback to compiler attribute, if available
N3652 general_constexpr cxx_relaxed_constexpr generalized constexpr no workaround
N3638 return_type_deduction none auto return type deduction for normal functions AUTO_RETURN macro
N3778 sized_deallocation none void operator delete(void *ptr, std::size_t size) no workaround
N3651 variable_template cxx_variable_templates template <typename T> T pi; no workaround

Get them all by specifying cpp14_lang.

The following features are not and will never be supported:

paper description reason
N3323 Tweak Certain C++ Contextual Conversions difficult to check, avoid relying on behavior
N3472 Binary Literals syntax sugar only
N3648 Generalized Lambda Capture lambdas are syntax sugar only
N3649 Generic Lambdas lambdas are syntax sugar only
N3653 Member initializers and aggregates difficult to check, no big user impact
N3664 Clarifying Memory Allocation wording change only
N3781 Digit seperator for literals syntax sugar only

C++14 library features [complete]

These features are all in the subdirectory cpp14_lib.

paper feature name example workaround, if any
N3668 exchange std::exchange() comp::exchange(), own implementation
N3421 generic_operator_functors std::greater<>{} comp::greater{} and the rest, no class templates!
N3658 integer_sequence std::index_sequence<4> comp::index_sequence<4> and co, own implementation
N3656 make_unique std::make_unique() comp::make_unique, own implementation
N3654 quoted ss >> std::quoted(str) no workaround, use boost
N3659 shared_lock std::shared_lock<std::shared_timed_mutex> no workaround
N3671 two_range_algorithm std::equal(first1, last1, first2, last2) comp::equal()/comp::mismatch()/comp::is_permutation(), own implementation

Get them all by specifying cpp14_lib.

The following features are not and will never be supported:

paper description reason
N3668 Fixing constexpr member functions without const workaround not possible, just avoid relying on that behavior
N3670 Addressing Tuples by Type just use index version
N3462 std::result_of and SFINAE impossible to check
N3545 operator() for std::integral_constant just "syntax" sugar
N3642 UDl's for standard library just "syntax" sugar
N3469 Constexpr for std::chrono no great workaround possible
N3470 Constexpr for std::array no great workaround possible
N3471 Constexpr for utilities no great workaround possible
N3657 Heterogeneous lookup optimization only, workaround for transparent functors supports this extension
N3655 Alias templates for traits just "syntax" sugar

C++17 language features

These features are all in the subdirectory cpp17_lang.

paper feature name example workaround, if any
N4295 fold_expressions return (args && ....); no workaround
N3928 terse_static_assert static_assert(condition); TERSE_STATIC_ASSERT(Cond) macro
N4267 utf8_char_literal char c = u8'A'; UTF8_ChAR_LITERAL(Str) macro taking a normal string, appending u8 prefix and converting it to a character

C++17 library features [up-to-date]

These features are all in the subdirectory cpp17_lib.

paper feature name example workaround, if any
N4389 bool_constant std::bool_constant comp::bool_constant
N4280 container_access std::size(cont) comp::size(cont), likewise for std::empty()/std::data()
N4169 invoke std::invoke(f) comp::invoke(f)
N4279 map_insertion m.try_emplace(key, value) comp::try_emplace(m, key, value), likewise for insert_or_assign()
N4508 shared_mutex std::shared_mutex no workaround
N4259 uncaught_exceptions std::uncaught_exceptions() no workaround, note the plural!
N3911 void_t std::void_t<int, char> comp::void_t<int, char>

Get them all by specifying cpp17_lib.

The following features are not and will never be supported:

paper description reason
N4190 Removing deprecated things removal, just don't use it
N4284 Contiguous iterator no actual code change
N4089 Conversion for std::unique_ptr<T[]> difficult to check, avoid relying on behavior
N4277 TriviallyCopyable std::reference_wrapper difficult to check, avoid relying on behavior
N4258 Cleaning-up noexcept difficult to check, no big impact on user
N4266 Missing SFINAE rule in std::unique_ptr difficult to check, no big impact on code, avoid relying on behavior
N4387 Improving constructor std::pair and std::tuple difficult to check, avoid relying on behavior
N4510 Minimal incomplete type support for containers difficult to check, avoid relying on behavior

Technical specifications

The technical specifications for the C++ standard library. These features are all in the subdirectory ts.

paper feature name description workaround, if any
N3804 any std::experimental::any class none, use boost or other implementation
N3915 apply std::experimental::apply(f, tuple) comp::apply(), own implementation
N4273 container_erausre std::experimental::erase_if(vector, pred) comp::erase_if()/comp::erase(), own implementation
P0013R1 logical_operator_traits std::experimental::disjunction comp::conjunction/comp::disjunction/comp::negation, own implementation
N4391 make_array std::experimental::make_array() comp::make_array()/comp::to_array(), own implementation
N4076 not_fn std::experimental::not_fn() comp::not_fn(), own implementation
N3793 optional std::experimental::optional none, use boost or other implementation
N3916 pmr Polymorphic memory resource only comp::memory_resource base class
N3921 string_view std::experimental::string_view none, use other implementation

Get them all by specifying ts.

Environment

Features regarding the general environment. These features are all in the subdirectory env. Note: These checks here aren't that great, it is recommended to set option explicitly.

feature name description workaround, if any
exception_support support for exception handling THROW(Ex), RETHROW_EX, fallback to std::abort(); TRY, fallback to if (true), CATCH_ALL, fallback to if (false)
hosted_implementation freestanding vs hosted alias macro HOSTED_IMPLEMENTATION, implementation of std::swap() and std::move()/std::forward() (if rvalue references are supported); those are otherwise not available
rtti_support support for RTTI comp::polymorhpic_cast, fallback to static_cast
threading_support support for threading no workaround

Get them all by specifying env.

Common extensions

These features are all in the subdirectory ext.

feature name example workaround, if any
assume __assume(cond) (from MSVC) ASSUME(x), fallback to nothing
bswap __builtin_bswap(x) (from GCC) ``comp::bswap(x)` (for unsigned fixed-sized integers), fallback to manual swap (constexpr)
clz __builtin_clz(x) (from GCC) comp::clz(x) (for unsigned fixed-sized integers), fallback to binary search (constexpr)
counter __COUNTER__ (most compilers) no workaround
ctz __builtin_ctz(x) (from GCC) comp::ctz(x) (for unsigned integers), fallback to binary search (constexpr)
expect __builtin_expect(x, val) (from GCC) EXPECT(x, val) (and LIKELY(cond),UNLIKELY(cond)), fallback to value itself
extension __extension__ (from GCC, marks extensions to silence warnings) EXTENSION, fallback to nothing
fallthrough [[clang::fallthrough]] (from clang) FALLTHROUGH, fallback to nothing
has_include __has_include(header) HAS_INCLUDE(x), fallback to always 0
int128 __int128 (from GCC) comp::(u)int128_t, no workaround, just convenience typedefs
popcount __builtin_popcount(x) (from GCC) comp::popcount(x) (for unsigned integers), fallback to bithacks (constexpr)
pretty_function __PRETTY_FUNCTION__ (from GCC) PRETTY_FUNCTION, fallback to __FUNCSIG__ on MSVC
unreachable __builtin_unreachable() (from GCC) UNREACHABLE, fallback to __assume(0) under MSVC, otherwise nothing
unused [[gnu::unused]] (from GCC) UNUSED, fallback to nothing; also MAKE_UNUSED(expr) that uses (void) trick

Get them all by specifying ext.cmake.

Contribution

As you probably noted, there are features missing. I wrote this library in a few hours and concentrated on the most important features for me. If you want to extend it or improve a workaround, please don't hesitate to fork and PR (or just write an issue and let me take care of it, when I have time, if you're lazy).

To write a new feature check, just create a new file in the appropriate subdirectory. Note: Do not include comp_base.cmake!.

Inside the feature file you should only use the following CMake functions in the given order:

  1. comp_api_version(major[.minor[.patch]]) - checks if the API has the required version. major must match exactly, the rest not higher than the API version. If there is a version mismatch, it is an error.

  2. comp_feature(<name> <test_code> <standard> <required...>) - does the feature check. name is the name of the feature without the directory, e.g. constexpr, not cpp11_lang/constexpr. test_code is the code that will be tested to see if the feature is supported. standard is the required C++ standard, this must be one of the COMP_CPPXX_FLAG values or COMP_CPP98_FLAG if no higher standard is needed. required is a list of requirede featuers that need to be supported in order to support this feature. If any of them isn't, this will not be checked.

  3. comp_workaround(<name> <workaround_code> <standard> <required...>) - writes workaround code (optional). name must be the same as in comp_feature(). workaround_code is the workaround code itself. It must use ${COMP_PREFIX} for macros and put anything else into the namespace ${COMP_NAMESPACE] (variable expansion works there, so write it exactly like that). The result of the test is available through ${COMP_PREFIX}HAS_NAME, e.g. #if ${COMP_PREFIX}HAS_CONSTEXPR ... #else ... #endif. standard is like in comp_feature() the standard required for the workaround code (the not-supported case, the supported case gets the standard of comp_feature()). required is a list of required features inside the workaround code. Their headers will be included prior to the workaround making it possible to use other workarounds.

  4. comp_sd6_macro(<name> <sd6_name> <value>) - writes SD 6 macro (optional, since 1.1). name must be the same as in comp_feature(). sd6_name is the SD-6 macro name and value its value.

  5. comp_unit_test(<name> <global_code> <test_code>) - defines a (Catch) unit test for the workaround code (optional). name must be the same as in comp_feature(). global_code will be put into the global namespace right after the including of the appropriate feature header and catch. test_code will be put into a Catch TEST_CASE().

The code for feature checking should be minimal and do not depend on any other advanced features (for example, do not use auto or nullptr) to prevent false failure. The workaround shouldn't use advanced features either, it can use other workarounds though.

The testing code should test the workaround. It will be run by the testing framework for both modes, supported and not supported.

Look at a few other files for example implementations.