How to limit maximum CXX_STANDARD to prevent accidental C++17 or above code

I have a C++ project built with CMake (an SDK), and a developer team that is used to coding in C++17 on other projects.

Project Requirements:

  • Only C++11 or C++14 language features must be used to compile the project. C++17 features that everyone loves like the <filesystem> header are prohibited
  • The project may require a new version of CMake like 3.28
  • The project must be portable to GCC, Clang, Windows, Linux and MacOS (no compile specific or OS specific solutions)

How do I enforce this in the build system, and cause the build to fail if a developer:

  1. Tries to modify the SDK and accidentally use C++17 features (happens all the time for the dev team who is used to coding in C++17
  2. Links to the SDK and tries to use C++17 in some dependent application code

Both the SDK, and consumers who link to it should be prohibited from using C++17 features.

Here is the minimum example:

main.cpp

#include <filesystem>

int main() {
    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.24)
project(cpp14_test)

set(CMAKE_CXX_STANDARD 14) 
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS  OFF)

add_executable(main main.cpp)

With this minimum reproducible example, the build is successful, even though I want it to fail with an include error that <filesystem> doesn’t exist. It does in C++17, but our requirements state we can’t use it.

I could come up with a more thorough example with a library and two projects to prove the PUBLIC properties work as expected, however, I think this will suffice for now.

The above method to set the standard is what I read as recommended in Craig Scott’s book, as well as his post on stack overflow, but it only imposes a minimum requirement, not a maximum cap on language features.

Related to:

https://cmake.org/cmake/help/v3.23/manual/cmake-compile-features.7.html#requiring-language-standards

CMake doesn’t provide a way to limit the language standard. It only provides a way to say “this thing needs at least language standard XXX”. A target can have a few different contributors to its required language standard, and CMake chooses the lowest language standard that satisfies all those requirements. It does not give you a way to say “don’t set the language standard to anything higher than XXX”.

That part of the book is going to be updated in the next edition (I’m actually in the middle of updating that specific part right now). With C++20 modules now potentially in the picture, things get a whole lot more complicated. You used to (typically) be able to get away with mixing different language standards in the one build, but you can’t any more once C++20 modules are involved. You need to have the same language standard used throughout or you run into problems with BMIs (built module interfaces). Not directly relevant to your specific query, but you’ll find the recommended pattern of setting the three CMAKE_... variables for the language standard and extensions will be more involved going forward.

1 Like

Ideas:

  • CI job that forces GCC / Clang with c++14 flag
  • dirty word source scanner that looks for c++17 includes
  • CI jobs that use old enough versions of various target compilers that don’t have most c++17 features while yet having the needed c++11/14 features

The latter approach is the most conventional and would allow you to say the project works all the way to vendor/version sets.

Includes are “just” filesystem lookups; I’m not sure how one would enforce this.

The best option is probably to do something like this in a “core” header that “everything” ends up including:

#if __cplusplus >= 201703L
#error "C++17 is not supported; use C++14 or older"
#endif
2 Likes

Thank you all for the suggestions. I’ll try implementing a few of the approaches and share my final approach. Ben’s was the most obvious to add to every file in the public header.

If you wanted to make it in CMake and not modify the existing source/headers, you could put that little snippet from Ben into a check_source_compiles and then fail the CMake configure if check is successful.

1 Like

Just curious. What is the rationale to forbid c++ 17?

Autosar C++14 and FACE™ 3.0 standards both limit to C++14. If one wants to certify the code, C++17 isn’t allowed, so it would be good to prohibit in CI rather than wait till certification time to realize you accidentally injected a bunch of C++17 code.

1 Like