Skip to content

Modernize code - #5114

Merged
kodiakhq[bot] merged 4 commits into
espressomd:pythonfrom
jngrad:cxx_20
Jul 18, 2025
Merged

Modernize code#5114
kodiakhq[bot] merged 4 commits into
espressomd:pythonfrom
jngrad:cxx_20

Conversation

@jngrad

@jngrad jngrad commented Jul 14, 2025

Copy link
Copy Markdown
Member

Description of changes:

  • bump version requirements for Boost, Caliper, Xcode (AppleClang)
  • use C++20 features more widely to reduce code complexity
  • make CMake more flexible w.r.t. required CXX and CUDA standard versions

jngrad added 2 commits July 11, 2025 17:17
Require AppleClang 17.0 (aka Xcode 16.3) and Boost 1.83.
@jngrad
jngrad force-pushed the cxx_20 branch 2 times, most recently from a70ad3a to 5a959d0 Compare July 15, 2025 18:54
Use range algorithms and concepts. Remove all `std::enable_if`.
Replace template partial specializations by constexpr conditionals.
Remove superfluous `typename`. Move global const variables defined
in header files to the corresponding source files.
Avoid modifying global variable CMAKE_CXX_STANDARD, which affects all
included projects and has lower precedence over per-target options.
@jngrad

jngrad commented Jul 15, 2025

Copy link
Copy Markdown
Member Author

Developer's notes:

  • boost::string_ref was replaced by boost::string_view in all Boost modules in Boost 1.63 (mailing list thread), but C++17 introduced std::string_view which provides the same functionality
  • trait definitions declared as templated static constexpr bool constants at class scope must be defined outside the class due to a bug in GCC < 14 (ticket), but not when declared as inheriting from std::integral_type types
  • Utils::Vector has two construction strategies: either a for loop (via copy_init() noexcept), or std::transform() (which isn't noexcept); both are need because nvcc is really strict about calling __device__ noexcept(true) functions from __host__ except(false) functions and vice versa
  • the Utils::Vector::Vector(Range const &rng) constructor participates in overload resolution with the Vector copy constructor and Vector cast operator (const-qualified), forcing the compiler to disambiguate the call by selecting the least const-qualified method, therefore static_cast<Utils::Vector3d> would almost never call the cast operator; there is no way to avoid this ambiguity, in fact when range-based constructors were introduced in C++23, STL containers solved the issue via tag dispatch using std::from_range (see e.g. constructor 6 in std::vector)
  • the Vector dot product was rewritten as a for loop to force inlining, since the std::inner_product isn't optimized out on Intel CPUs (closes Inline-friendly dot product. #5113)

@jngrad
jngrad marked this pull request as ready for review July 15, 2025 23:30
@jngrad
jngrad requested a review from reinaual July 15, 2025 23:30
@jngrad

jngrad commented Jul 15, 2025

Copy link
Copy Markdown
Member Author

Regarding the required versions of C, C++ and CUDA (more precisely "C++/CUDA", as it controls CUDA's C++ standard), the CMake logic is now more flexible and should allow us to make a gradual transitions to C++23, for example by bumping C++ code to C++23 while keeping CUDA code at C++20 (hard requirement of CUDA 12.x).

Until now, we hardcoded the CMAKE_CXX_STANDARD/CMAKE_CXX_STANDARD_REQUIRED/CMAKE_CXX_EXTENSIONS global CMake variables and their CUDA equivalents. Their role is to bump up the C/CXX/CUDA version of all target in the project and subprojects at the user's discretion (via cmake .. -D ), or at the discretion of the developer of a "final" CMake project (i.e. a project that isn't designed to be included in a parent CMake project). Until recently, ESPResSo was considered to be a final project, and we hardcoded these variables to make all object files use a consistent C++ standard. But ESPResSo is now slowly becoming a library, for example VOTCA and pyMBE are using ESPResSo as a dependency, and the ESPResSo core now has a relatively stable API that allows simulation scripts to be written in C++.

Instead of hardcoding these global variables, we can set the cxx_std_20 and cuda_std_20 properties on our targets to indicate their minimal supported C++ standard. This way both developers and CMake parent projects have the freedom to bump up the C++ standard to 23 with a single variable change. One practical use case for this CMake feature is verifying that ESPResSo still compiles with the next C++ standard, and thus guarantee we are not using deprecated C++ features that have been removed from the next version of the C++ standard. This is actually done in our CI since June 2024. The c_std_11/cxx_std_20/cuda_std_20 properties only provide a minimum, and are overridden upward by global variables.

MWE:

cmake_minimum_required(VERSION 3.24)
project(MyProject LANGUAGES CXX)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_library(core SHARED a.cc)
if("cxx_std_20" IN_LIST CMAKE_CXX_COMPILE_FEATURES)
  target_compile_features(core PUBLIC cxx_std_20)
endif()
$ echo "int foo() {return 1;}" > a.cc
$ mkdir build
$ cd build
$ cmake ..
$ grep -Po ".std=[^ ]+" compile_commands.json
-std=gnu++20
$ rm -rf *
$ cmake .. -D CMAKE_CXX_STANDARD=23
$ grep -Po ".std=[^ ]+" compile_commands.json
-std=gnu++23

You can check the effect in a local build of a CMake-based project with:

import re
import json
with open("compile_commands.json") as f:
    data = json.load(f)

summary = []
for d in data:
    m = re.search("-std=[^ ]+", d["command"])
    std = m.group(0) if m else "default"
    summary.append((std, d["file"]))

for x in sorted(summary):
    print(" ".join(x))

Technical details:

  • property INTERFACE_COMPILE_FEATURES cxx_std_20 is transitive, but CXX_EXTENSIONS OFF isn't and needs to be manually added to all dependent targets (see ticket)
    • -std=c++20 is the C++20 standard, -std=gnu++20 is the C++20 standard with GCC extensions (available in Clang)
    • -std=c11 is the C11 standard, -std=gnu11 is the C11 standard with GCC extensions (available in Clang)
  • target_compile_features(core INTERFACE cxx_std_23) mandates at least C++23 and allows promotion to C++26 via option -DCMAKE_CXX_STANDARD=26, whereas the combo set_target_properties(core PROPERTIES CXX_STANDARD 23) plus set_target_properties(core PROPERTIES CXX_STANDARD_REQUIRED ON) doesn't allow promotion (useful for backward compatibility with codes that leverage deprecated features removed in recent versions of the standard)
  • this PR introduces a minimal C standard version for completeness, because we have C dependencies: ScaFaCoS, Caliper (C11 minimum), waLBerla (via sqlite3 external dependency); this leaves the door open to future refactoring, now that CMake 3.25.2+ fully supports targets with mixed sources (C++ plus CUDA, or C++ plus C)

@jngrad jngrad added the automerge Merge with kodiak label Jul 18, 2025
@kodiakhq
kodiakhq Bot merged commit 8f33f31 into espressomd:python Jul 18, 2025
10 checks passed
@jngrad
jngrad deleted the cxx_20 branch July 18, 2025 13:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants