Crascit

Caffeine-powered software

Enabling C++11 And Later In CMake

With the recent evolution of C++, build systems have had to deal with the complication of selecting the relevant compiler and linker flags. If your project targets multiple platforms and compilers, this can be a headache to set up. Happily, with features added in CMake 3.1, it is trivial to handle this in a generic way.

Setting the C++ standard directly

The simplest way to use a particular C++ standard in your project is to add the following two variable definitions before you define any targets:

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

Valid values for CMAKE_CXX_STANDARD are 98, 11 and 14. This variable is used as the default for the CXX_STANDARD target property, so all targets defined after the variable is set will pick up the requirement. The behaviour of the CXX_STANDARD target property mostly behaves as you would expect, adding the relevant compiler and linker flags to the target to make it build with the specified C++ standard. There is one minor wrinkle in that if the compiler doesn’t support the specified standard, rather than causing a warning or error, by default CMake falls back to the previous standard instead. To prevent this fallback behaviour, the CXX_STANDARD_REQUIRED target property should be set to YES. Since you will likely be wanting to disable the fallback behaviour in most situations, you will probably find it easier to just set the CMAKE_CXX_STANDARD_REQUIRED variable to YES instead, since it acts as the default for the CXX_STANDARD_REQUIRED target property.

Projects will also probably want to set the following too:

set(CMAKE_CXX_EXTENSIONS OFF)

On some platforms, this results in linking to a different library (e.g. -std=c++11 rather than -std=gnu++11). The default behavior is for C++ extensions to be enabled. Credit to ajneu in the comments for pointing out this particular variable and its effects.

And that’s all it takes to get CMake to set the compiler and linker flags appropriately for a specific C++ standard. In most cases, you probably want to use a consistent C++ standard for all targets defined in a project, so setting the global CMAKE_CXX_STANDARD, CMAKE_CXX_STANDARD_REQUIRED and CMAKE_CXX_EXTENSIONS variables would be the most convenient. Where your CMakeLists.txt might be getting included from some higher level project or if you don’t want to set the global behaviour, then setting the CXX_STANDARD, CXX_STANDARD_REQUIRED and CXX_EXTENSIONS properties on each target will achieve the same thing on a target-by-target basis. For example:

set_target_properties(myTarget PROPERTIES
    CXX_STANDARD 11
    CXX_STANDARD_REQUIRED YES
    CXX_EXTENSIONS NO
)

Setting the C++ standard based on features

For most situations, the above method is the simplest and most convenient way to handle the compiler and linker flags for a particular C++ standard. As an alternative, CMake also offers a finer grained approach, allowing you to specify the compiler features that your code requires and letting CMake work out the C++ standard automatically. Initial support for compiler features was made available in CMake 3.1 for just a small number of compilers, expanded to a broader set of compilers in version 3.2 and from 3.6 all commonly used compilers are supported.

To specify that a particular target requires a certain feature, CMake provides the target_compile_features() command:

target_compile_features(<target> <PRIVATE|PUBLIC|INTERFACE> <feature> [...])

The PRIVATE, PUBLIC and INTERFACE keywords work just like they do for the various other target_... commands, controlling whether the feature requirement should apply to just this target (PRIVATE), this target and anything that links to it (PUBLIC) or just things that link to it (INTERFACE). The supported feature entries depend on the compiler being used. The full set of features CMake knows about is included in the CMake documentation and can also be obtained from the CMAKE_CXX_KNOWN_FEATURES global property (so you need to use get_property() to access it). To see the subset of features supported by your current compiler, place the following line anywhere after your project() command:

message("Supported features = ${CMAKE_CXX_COMPILE_FEATURES}")

The following example tells CMake that your target requires support for variadic templates and the nullptr keyword in its implementation and its public interface, plus it also uses lambda functions internally:

target_compile_features(myTarget
    PUBLIC
        cxx_variadic_templates
        cxx_nullptr
    PRIVATE
        cxx_lambdas
)

You can also use generator expressions with target_compile_features(). The following example adds a feature requirement for compiler attributes only when using the GNU compiler:

target_compile_features(myTarget
    PUBLIC $<$<CXX_COMPILER_ID:GNU>:cxx_attributes>
)

Using compiler features instead of unilaterally setting the C++ standard for a target has some advantages and disadvantages. On the plus side, you can specify precisely the features you want without having to know which version of the C++ standard supports them. You also get the flexibility of being able to use generator expressions. One obvious drawback is that the list of features your code uses might be quite long and it would be easy to miss features added to the code later. More importantly, if your target links against other third party libraries, you need to ensure your target links to the same C++ standard library that they do or you will get undefined symbols at link time.

Optional features

Your code base may be flexible enough to support features of later C++ standards if available, but still build successfully without them. For such situations, the compile features approach is convenient because it allows generator expressions to control whether or not a particular feature is used based on whether or not the compiler supports that feature. The following example adapted slightly from the CMake documentation will choose between two different directories to add to the include path for anything linking against myLib:

add_library(myLib ...)
set(with_variadics ${CMAKE_CURRENT_SOURCE_DIR}/with_variadics)
set(no_variadics ${CMAKE_CURRENT_SOURCE_DIR}/no_variadics)
target_include_directories(myLib
    INTERFACE
        "$<$<COMPILE_FEATURES:cxx_variadic_templates>:${with_variadics}>"
        "$<$<NOT:$<COMPILE_FEATURES:cxx_variadic_templates>>:${no_variadics}>"
)

The COMPILE_FEATURES generator expression tests whether the named feature is supported by the compiler. In the above example, if the compiler supports variadic templates, the with_variadics subdirectory will be added to the header search path, whereas if the compiler does not support them, then the no_variadics subdirectory will be added instead. This approach could even be used with the target_sources() command to conditionally include different source files based on compiler features.

Concluding remarks

Where possible, favour simplicity. Compile features are very flexible, but if you know your code requires a specific C++ standard, it is much cleaner to simply tell CMake to use that standard with the CXX_STANDARD target property or the default CMAKE_CXX_STANDARD global variable. This is particularly relevant when linking against third party libraries which may have been built with a specific C++ standard library. If, for example, you link against any Boost modules which have static libraries, you will need to link to the same C++ standard library as was used when Boost was built or else you will get many unresolved symbols at link time.

Also consider using the most recent CMake release your project can require, since later CMake versions have broader support for more compilers (e.g. support for Intel compilers was only added in CMake 3.6).

Previous

Practical uses for variadic templates

Next

Handling binary assets and test data with CMake

6 Comments

  1. ajneu

    I use

    set(CMAKE_CXX_STANDARD 11) ## or 14 !!
    set(CMAKE_CXX_EXTENSIONS OFF) ## on g++ this ensures: -std=c++11 and not -std=gnu++11

    • Joe

      Thank you very much for the information. If it weren’t for your comment, I would have never noticed that g++ defaults to GNU standards. 🙂

  2. ” rather than causing a warning or error, by default CMake falls back to the previous standard instead To prevent this fallback behaviour, the CXX_STANDARD_REQUIRED target property should be set to YES.” The default choice is the opposite of what the default choice should be, most people will want CXX_STANDARD_REQUIRED, so why not default that to YES? The few cases where a fallback to the old standard is needed can set CXX_STANDARD_REQUIRED to NO. Seems like a no-brainer? What am I missing?

    • I share your view! I also have a hard time understanding why the default is the way it is, but that’s what the CMake developers chose. You could always ask on the CMake developer’s list, but since it is already released behaviour, I’d be surprised if they were willing to change it.

  3. Bob

    Hi!

    Great blog post. However there’s one small but very important detail you should mention:

    CXX_STANDARD: For compilers that have no notion of a standard level, such as MSVC, this has no effect.

    It is important to be aware of that, it means you need to take care of MSVC separately – that makes this feature basically useless because it means you have to use the way via target_compile_features anyway to make it work across all compilers.

    • Visual Studio C++ 2015 Update 3 introduced some compiler options to control the standard version (see this issue in Kitware’s issue tracker which mentions this). Prior to this, you were stuck with whatever version was implemented by the MSVC version you were using. In those cases, even target_compile_features() won’t really help you much other than reporting an error if the feature you want isn’t available. I’d expect a future CMake release will add support for those options eventually though.

      In practice, most people understand MSVC is a special case. For a cross-platform project, the understanding is that it wants some minimum C++ standard version. Step 1 is to ensure they are using a compiler that support it on each platform. Step 2 is to use the correct flags to get the desired behaviour and features. In the case of MSVC, step 1 also happens to take care of step 2 because the compiler enables the highest C++ standard it supports by default, which is the opposite to most other common compilers. For all other compilers, the CMake features discussed in this article are necessary to enable C++11 or higher.

Leave a Reply

Powered by WordPress & Theme by Anders Norén

%d bloggers like this: