C++ standard, warnings and definitions

CMake · Practical · 10 min

Every project needs consistent language settings and warnings, set in a way that works on GCC, Clang and MSVC.

Two ways to request a language standard:

  • Per target: target_compile_features(core PUBLIC cxx_std_20). As a usage requirement it propagates, so anything using core's headers is compiled as C++20 or newer.
  • Project-wide defaults: set(CMAKE_CXX_STANDARD 20), set(CMAKE_CXX_STANDARD_REQUIRED ON) (fail instead of silently falling back) and set(CMAKE_CXX_EXTENSIONS OFF) (use -std=c++20, not -std=gnu++20). Set them before creating targets.

Warnings are compiler-specific, so branch on the compiler. Keep them PRIVATE: your warnings shouldn't be forced on your users.

if(MSVC)
  target_compile_options(core PRIVATE /W4 /permissive-)
else()
  target_compile_options(core PRIVATE -Wall -Wextra -Wpedantic)
endif()

Treating warnings as errors is great in CI but annoying for people building your release with a newer compiler; CMake's COMPILE_WARNING_AS_ERROR property (3.24+) can be switched off from the command line with --compile-no-warning-as-error.

Definitions become preprocessor macros: target_compile_definitions(core PRIVATE APP_VERSION="${PROJECT_VERSION}"). For many values, configure_file(version.h.in version.h) generates a header from a template with @PROJECT_VERSION@ placeholders, and a changed value rebuilds only what includes it.

Example

cmake_minimum_required(VERSION 3.25)
project(app VERSION 2.1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)     # compile_commands.json for clangd etc.

add_executable(app main.cpp)
if(MSVC)
  target_compile_options(app PRIVATE /W4 /permissive-)
else()
  target_compile_options(app PRIVATE -Wall -Wextra -Wpedantic)
endif()
set_target_properties(app PROPERTIES COMPILE_WARNING_AS_ERROR ON)

configure_file(version.h.in version.h)    # contains: #define APP_VERSION "@PROJECT_VERSION@"
target_include_directories(app PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_compile_definitions(app PRIVATE APP_NAME="${PROJECT_NAME}")

# compile line: c++ -DAPP_NAME="app" -I.../build -std=c++20 -Wall -Wextra -Wpedantic -Werror ...

Remember

  • cxx_std_20 per target, or CMAKE_CXX_STANDARD with _REQUIRED ON and EXTENSIONS OFF
  • Branch on MSVC for warning flags and keep them PRIVATE
  • configure_file generates headers from templates with project values

Watch out for

  • Setting CMAKE_CXX_STANDARD after add_executable; it only applies to targets created afterwards.

Check yourself

  1. Why should warning flags be PRIVATE?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: cmake.org/cmake/help/latest/manual/cmake-compile-features.7.html