Professional CMake:

A Practical Guide

Learn to use CMake effectively with practical advice from a CMake co-maintainer. You can also have the author work directly with your team!

Enabling C++11 And Later In CMake

Updated June 2020

With the constant 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, with 17 also being added in CMake 3.8 and 20 added in CMake 3.12. 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 CXX_STANDARD target property mostly behaves as you would expect. It results in adding the relevant compiler and linker flags to the target to make it build with the specified C++ standard. There is a minor wrinkle in that if the compiler doesn’t support the specified standard, by default CMake falls back to the latest standard the compiler does support instead. It does not generate an error by default. 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. Note that CMake may still end up selecting a more recent language standard than the one specified (see the discussion of compiler features in the next section).

Projects will also probably want to set the following too:

set(CMAKE_CXX_EXTENSIONS OFF)

This results in modifying the flag used to set the language standard (e.g. -std=c++11 rather than -std=gnu++11). The default behavior is for C++ extensions to be enabled, but for broadest compatibility across compilers, projects should prefer to explicitly disable them. 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 and the language standard requirements are propagated to dependencies through PUBLIC and INTERFACE specifications. 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. As a compromise, CMake 3.8 introduced the ability to specify the minimum language standard as a compiler feature. These meta features, as they are called, have names of the form cxx_std_YY, where YY is one of the same values supported by the CXX_STANDARD target property. When building the target, CMake will use the latest language standard required by the CXX_STANDARD target property or by any compiler feature set for that target. In the following example, foo would be built with C++14 and bar with C++17:

set_target_properties(foo PROPERTIES CXX_STANDARD 11)
target_compile_features(foo PUBLIC cxx_std_14)

set_target_properties(bar PROPERTIES CXX_STANDARD 17)
target_compile_features(bar PUBLIC cxx_std_11)

It should be noted that fine-grained compile features are no longer being added beyond C++14. They proved to be incomplete at best and were difficult to maintain. Only the cxx_std_YY meta-features are being added for C++17 and beyond.

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. 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. Later CMake versions have broader support for more compilers (e.g. support for Intel compilers was only added in CMake 3.6). If your project and all the dependencies it links against do not have to support CMake versions earlier than 3.8, the use of compiler features to specify the language standard directly may be considered. Note, however, that unlike target properties, there is no way to set a global default for compiler features, so they would have to be specified on each target that had a requirement for a particular minimum language standard. CMake may then upgrade some targets to later standards if their dependencies demand it through PUBLIC or INTERFACE compiler features.

It is also worth noting that language standard requirements are not restricted to just C++. The various target properties, variables and compiler features have predictably named equivalents for C as well (e.g. C_STANDARD target property, c_std_YY compiler meta feature). CMake 3.8 also introduced language standard specifications for CUDA and the try_compile() command learnt to support language standard requirements.


Have a CMake maintainer work on your project

Get the book for more CMake content

16 thoughts on “Enabling C++11 And Later In CMake”

    • 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. 🙂

      Reply
  1. ” 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?

    Reply
    • 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.

      Reply
  2. 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.

    Reply
    • 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.

      Reply
  3. What do you do if you are writing a library that needs at least C++11 features? Do you set the C++11 requirements on the targets? (Assuming library projects should not set CMAKE_CXX_STANDARD etc. to allow the master project to control this if built as part of a sub-project.)

    Also, libraries might need a certain C++ standard for building the library itself, but the consumer of the API might not need it at all, the header interfaces might have a lower requirement. How should you denote that in a library interface?

    Reply
  4. Start by making a distinction between whether building your library needs C++11 and whether consuming it needs C++11. For example, do any of your headers make use of C++11 features? If so, then that’s a PUBLIC requirement, which you can express using target_compile_features(myTarget PUBLIC cxx_std_11). If there are no C++11 features in your headers, then you can make it PRIVATE instead. BUT…..

    From a pure point of view, one should not mix different C++ standard versions in the one program, including the libraries it links to. In practice, however, it is typically fine, with some exceptions (e.g. std::auto_ptr being removed in C++17).. As a rule of thumb, I’d suggest that consumers should be built with at least the same C++ standard level or higher of any library it uses. Going the other way where an executable is built with a lower C++ standard than libraries it uses, I’m not sure if that typically works. It might appear to work, but could be masking some subtle issues. You’d need to discuss that with someone more intimately familiar with compiler implementation details.

    When this article was written, the language standard level compile feature was still relatively new to CMake. These days, it’s been available for long enough that you can reasonable consider relying on it being available (I think CMake 3.8 is about where it became available).

    Reply
  5. So using target_compile_features(myTarget PUBLIC cxx_std_11) is the way to message that the lib was made for C++11 or newer? (From what I understand, target_compile_features does not force an exact match to C++11, it can be greater, right?) No need for any other checks to be a good library citizen?

    I’ve noticed how much progressions there has been with CMake in the recent years. There’s even stuff in 3.15 and 3.16 that greatly simplify things to the point I’ve set minimum to these versions.

    Reply
  6. Whether you use target_compile_features(myTarget PUBLIC cxx_std_11) or you set CXX_STANDARD and CXX_STANDARD_REQUIRED, both methods ultimately specify a minimum standard version, not an absolute one. CMake can always use a higher standard level if it deems it necessary to meet some constraint. These days, using compile features like cxx_std_11 would probably be the better way. It can be used to specify transitive requirements, whereas the CXX_STANDARD property cannot. The main caveat (apart from requiring CMake 3.8 or later) is that you still need to specify CXX_STANDARD if you also want to set CXX_EXTENSIONS due to the way the compiler flags work. You have to specify a standard level to be able to specify whether or not you want to enable extensions. It’s unfortunate that these two things are rolled together into one flag for GCC-like compilers, but we’re stuck with it.

    Reply
  7. This does not appear to set the correct clang setting in my Xcode 11 (or 12) projects. All this does is add -std=c++17 and -libc++ to the command lines. The following lines are the only way I get correct settings in the project. I’ve kept them for Windows.

    helper to set clang settings

    macro (set_xcode_property TARGET XCODE_PROPERTY XCODE_VALUE)
        set_property (TARGET ${TARGET} PROPERTY 
            XCODE_ATTRIBUTE_${XCODE_PROPERTY} ${XCODE_VALUE})
    endmacro (set_xcode_property)
    
    if (APPLE)
        set_xcode_property(${myTarget} CLANG_CXX_LANGUAGE_STANDARD "c++17")
        set_xcode_property(${myTarget} CLANG_CXX_LIBRARY "libc++")
        set_xcode_property(${myTarget} CLANG_X86_VECTOR_INSTRUCTIONS "avx2")
        set_xcode_property(${myTarget} GCC_ENABLE_CPP_EXCEPTIONS NO)
        set_xcode_property(${myTarget} GCC_ENABLE_CPP_RTTI NO)
    endif (APPLE)
    

    these don’t set correct setting in Xcode, but keep for Win

    if (WIN32)
        set_target_properties(${myTarget} PROPERTIES
            CXX_STANDARD 17
            CXX_STANDARD_REQUIRED YES
            CXX_EXTENSIONS NO
            CXX_EXCEPTIONS OFF
            CXX_RTTI OFF
        )
    
    endif (WIN32)
    
    Reply
  8. As far as I’m aware, the CXX_STANDARD and CXX_EXTENSIONS target properties still work with Xcode 11 and 12, it’s just that they set compiler flags directly rather than using the dedicated language settings (i.e. the Apple Clang - Language - C++ section). The end result should be the same, it just looks a bit weird in the Xcode project. Feel free to report this in the CMake issue tracker as something that could be improved. It is possible that earlier Xcode versions didn’t have dedicated fields for this and the Xcode generator hasn’t been updated to use these fields for newer versions.

    Reply
  9. Hi. I’m setting target_compile_features(somelib PUBLIC cxx_std_14) but I don’t see g++ getting the -std=c++14 flag passed to it. What am I missing?

    Reply
  10. I suggest you post this question on the CMake forum. Be sure to include a minimal project that reproduces your problem, it will greatly increase the chances of someone being able to identify the underlying issue.

    Reply
  11. I figured it out. Apparently, CMake only adds flags if needed. I’m using gcc v9.3.0 and I was asking for C++14 level support. I did not see -std=c++14 in the compile lines so I assumed it didn’t work. When I changed the level to C++17 or C++20 Cmake did add the appropriate flag to the compile line. I assume that means that gcc v9.3.0 defaults to C++14 level support and so does not require the flag. I can’t say I agree with CMake on this as I would have felt better seeing the flag regardless of the compiler’s default standard support level.

    Reply

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.