From 2bd539568d2ccc96e76f461360dc0888231c92cb Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 19 Aug 2026 22:10:34 -0400 Subject: [PATCH 1/3] feat: drop C++11 support, require C++14 or newer The C++14 code paths become unconditional: std alias templates, std::make_index_sequence, lambda init-captures in py::init factories, [[deprecated]], and overload_cast. The CMake interface now requires cxx_std_14, setup_helpers rejects cxx_std below 14, and CI C++11 jobs move to C++14. Assisted-by: ClaudeCode:claude-fable-5 --- .github/workflows/ci.yml | 80 ++++++---------- .github/workflows/upstream.yml | 24 ++--- CMakeLists.txt | 3 +- docs/advanced/deprecated.rst | 4 +- docs/basics.rst | 2 +- docs/classes.rst | 17 +--- docs/compiling.rst | 6 +- include/pybind11/detail/argument_vector.h | 2 +- include/pybind11/detail/common.h | 93 +++---------------- include/pybind11/detail/init.h | 32 +------ include/pybind11/pybind11.h | 2 +- pybind11/setup_helpers.py | 28 +++--- tests/extra_setuptools/test_setuphelper.py | 10 +- tests/pybind11_tests.cpp | 4 +- .../installed_target/CMakeLists.txt | 2 + .../subdirectory_target/CMakeLists.txt | 2 + tests/test_cross_module_rtti/CMakeLists.txt | 2 +- tools/pybind11Common.cmake | 20 ++-- 18 files changed, 105 insertions(+), 228 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb2fcf2b67..d0342c41c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,7 +99,7 @@ jobs: cmake-args: -DCMAKE_CXX_STANDARD=14 - runs-on: macos-15-intel python-version: '3.13' - cmake-args: -DCMAKE_CXX_STANDARD=11 + cmake-args: -DCMAKE_CXX_STANDARD=14 - runs-on: macos-latest python-version: '3.12' cmake-args: -DCMAKE_CXX_STANDARD=17 -DPYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION=ON @@ -152,7 +152,7 @@ jobs: python-version: ${{ matrix.python-version }} cmake-args: ${{ matrix.cmake-args }} - # This checks inplace builds with C++11 + # This checks inplace builds with C++14 inplace: if: github.event.pull_request.draft == false strategy: @@ -254,12 +254,15 @@ jobs: run: uv tool install ninja - name: Configure via preset - run: cmake --preset venv -DPYBIND11_CREATE_WITH_UV="${{ matrix.python-version }}" + run: > + cmake --preset venv + -DPYBIND11_CREATE_WITH_UV="${{ matrix.python-version }}" + -DCMAKE_CXX_STANDARD=14 - - name: Build C++11 + - name: Build C++14 run: cmake --build --preset venv - - name: Python tests C++11 + - name: Python tests C++14 run: cmake --build --preset testsvenv -t pytest # Testing on clang using the excellent silkeh clang docker images @@ -536,43 +539,43 @@ jobs: set +e; source /opt/intel/oneapi/setvars.sh; set -e python3 -m pip install -r tests/requirements.txt - - name: Configure C++11 + - name: Configure C++14 run: | set +e; source /opt/intel/oneapi/setvars.sh; set -e - cmake -S . -B build-11 \ + cmake -S . -B build-14 \ -DPYBIND11_WERROR=ON \ -DDOWNLOAD_CATCH=ON \ -DDOWNLOAD_EIGEN=OFF \ - -DCMAKE_CXX_STANDARD=11 \ + -DCMAKE_CXX_STANDARD=14 \ -DCMAKE_CXX_COMPILER=$(which icpc) \ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - - name: Build C++11 + - name: Build C++14 run: | set +e; source /opt/intel/oneapi/setvars.sh; set -e - cmake --build build-11 -j 2 -v + cmake --build build-14 -j 2 -v - - name: Python tests C++11 + - name: Python tests C++14 run: | set +e; source /opt/intel/oneapi/setvars.sh; set -e sudo service apport stop - cmake --build build-11 --target check + cmake --build build-14 --target check - - name: C++ tests C++11 + - name: C++ tests C++14 timeout-minutes: 3 run: | set +e; source /opt/intel/oneapi/setvars.sh; set -e - cmake --build build-11 --target cpptest + cmake --build build-14 --target cpptest - - name: Interface test C++11 + - name: Interface test C++14 run: | set +e; source /opt/intel/oneapi/setvars.sh; set -e - cmake --build build-11 --target test_cmake_build + cmake --build build-14 --target test_cmake_build - name: Visibility test run: | set +e; source /opt/intel/oneapi/setvars.sh; set -e - cmake --build build-11 --target test_cross_module_rtti + cmake --build build-14 --target test_cross_module_rtti - name: Configure C++17 run: | @@ -659,7 +662,7 @@ jobs: -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_STANDARD=11 + -DCMAKE_CXX_STANDARD=14 -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") - name: Build @@ -810,7 +813,7 @@ jobs: -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON ${{ matrix.args }} - - name: Build C++11 + - name: Build C++14 run: cmake --build build -j 2 - name: Python tests @@ -859,7 +862,7 @@ jobs: -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON ${{ matrix.args }} - - name: Build C++11 + - name: Build C++14 run: cmake --build build --config Debug -j 2 - name: Python tests @@ -969,25 +972,25 @@ jobs: - uses: actions/checkout@v7 - - name: Configure C++11 + - name: Configure C++14 # LTO leads to many undefined reference like # `pybind11::detail::function_call::function_call(pybind11::detail::function_call&&) run: >- - cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=11 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON + cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") -S . -B build - - name: Build C++11 + - name: Build C++14 run: cmake --build build -j 2 - - name: Python tests C++11 + - name: Python tests C++14 run: cmake --build build --target pytest -j 2 - - name: C++11 tests + - name: C++14 tests timeout-minutes: 3 run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build --target cpptest -j 2 - - name: Interface test C++11 + - name: Interface test C++14 run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build --target test_cmake_build - name: Visibility test @@ -996,31 +999,6 @@ jobs: - name: Clean directory run: git clean -fdx - - name: Configure C++14 - run: >- - cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=14 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON - -DPYTHON_EXECUTABLE=$(python -c "import sys; print(sys.executable)") - -S . -B build2 - - - name: Build C++14 - run: cmake --build build2 -j 2 - - - name: Python tests C++14 - run: cmake --build build2 --target pytest -j 2 - - - name: C++14 tests - timeout-minutes: 3 - run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build2 --target cpptest -j 2 - - - name: Interface test C++14 - run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build2 --target test_cmake_build - - - name: Visibility test - run: PYTHONHOME=/${{matrix.sys}} PYTHONPATH=/${{matrix.sys}} cmake --build build2 --target test_cross_module_rtti - - - name: Clean directory - run: git clean -fdx - - name: Configure C++17 run: >- cmake -G "MinGW Makefiles" -DCMAKE_CXX_STANDARD=17 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON diff --git a/.github/workflows/upstream.yml b/.github/workflows/upstream.yml index 7ad8c99014..2ac1fd9a63 100644 --- a/.github/workflows/upstream.yml +++ b/.github/workflows/upstream.yml @@ -49,28 +49,28 @@ jobs: cmake --version pip list - # First build - C++11 mode and inplace - - name: Configure C++11 + # First build - C++14 mode and inplace + - name: Configure C++14 run: > - cmake -S . -B build11 + cmake -S . -B build14 -DPYBIND11_WERROR=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON - -DCMAKE_CXX_STANDARD=11 + -DCMAKE_CXX_STANDARD=14 -DCMAKE_BUILD_TYPE=Debug - - name: Build C++11 - run: cmake --build build11 -j 2 + - name: Build C++14 + run: cmake --build build14 -j 2 - - name: Python tests C++11 - run: cmake --build build11 --target pytest -j 2 + - name: Python tests C++14 + run: cmake --build build14 --target pytest -j 2 - - name: C++11 tests + - name: C++14 tests timeout-minutes: 3 - run: cmake --build build11 --target cpptest -j 2 + run: cmake --build build14 --target cpptest -j 2 - - name: Interface test C++11 - run: cmake --build build11 --target test_cmake_build + - name: Interface test C++14 + run: cmake --build build14 --target test_cmake_build # Second build - C++17 mode and in a build directory - name: Configure C++17 diff --git a/CMakeLists.txt b/CMakeLists.txt index 65f794a49d..2ef6af928f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -294,8 +294,7 @@ if(NOT TARGET pybind11_headers) pybind11_headers ${pybind11_system} INTERFACE $ $) - target_compile_features(pybind11_headers INTERFACE cxx_inheriting_constructors cxx_user_literals - cxx_right_angle_brackets) + target_compile_features(pybind11_headers INTERFACE cxx_std_14) if(NOT "${PYBIND11_INTERNALS_VERSION}" STREQUAL "") target_compile_definitions( pybind11_headers INTERFACE "PYBIND11_INTERNALS_VERSION=${PYBIND11_INTERNALS_VERSION}") diff --git a/docs/advanced/deprecated.rst b/docs/advanced/deprecated.rst index c9f4a2e666..fde0423f47 100644 --- a/docs/advanced/deprecated.rst +++ b/docs/advanced/deprecated.rst @@ -3,8 +3,8 @@ Deprecated ########## -Support for C++11 is deprecated and will be removed in a future version. Please -use at least C++14. +Support for C++11 was removed in pybind11 3.1. Please use at least C++14; +C++17 or newer is recommended. Support for FindPythonLibs (not available in CMake 3.26+ mode) is deprecated and will be removed in a future version. The default mode is also going to diff --git a/docs/basics.rst b/docs/basics.rst index 7f77f201fe..8bf0d12f04 100644 --- a/docs/basics.rst +++ b/docs/basics.rst @@ -143,7 +143,7 @@ On Linux, the above example can be compiled using the following command: .. code-block:: bash - $ c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3 -m pybind11 --extension-suffix) + $ c++ -O3 -Wall -shared -std=c++14 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3 -m pybind11 --extension-suffix) .. note:: diff --git a/docs/classes.rst b/docs/classes.rst index 97ad72f3f6..a24e182f77 100644 --- a/docs/classes.rst +++ b/docs/classes.rst @@ -414,8 +414,7 @@ The overload signatures are also visible in the method's docstring: | | Set the pet's name -If you have a C++14 compatible compiler [#cpp14]_, you can use an alternative -syntax to cast the overloaded function: +You can also use an alternative syntax to cast the overloaded function: .. code-block:: cpp @@ -439,20 +438,6 @@ on constness, the ``py::const_`` tag should be used: .def("foo_mutable", py::overload_cast(&Widget::foo)) .def("foo_const", py::overload_cast(&Widget::foo, py::const_)); -If you prefer the ``py::overload_cast`` syntax but have a C++11 compatible compiler only, -you can use ``py::detail::overload_cast_impl`` with an additional set of parentheses: - -.. code-block:: cpp - - template - using overload_cast_ = pybind11::detail::overload_cast_impl; - - py::class_(m, "Pet") - .def("set", overload_cast_()(&Pet::set), "Set the pet's age") - .def("set", overload_cast_()(&Pet::set), "Set the pet's name"); - -.. [#cpp14] A compiler which supports the ``-std=c++14`` flag. - .. note:: To define multiple overloaded constructors, simply declare one after the diff --git a/docs/compiling.rst b/docs/compiling.rst index a6bee86ffe..8c8ec2af72 100644 --- a/docs/compiling.rst +++ b/docs/compiling.rst @@ -78,7 +78,7 @@ that. Your ``meson.build`` file would look something like this: 'cpp', version: '0.1.0', default_options: [ - 'cpp_std=c++11', + 'cpp_std=c++14', ], ) @@ -649,7 +649,7 @@ On Linux, you can compile an example such as the one given in .. code-block:: bash - $ c++ -O3 -Wall -shared -std=c++11 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix) + $ c++ -O3 -Wall -shared -std=c++14 -fPIC $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix) The ``python3 -m pybind11 --includes`` command fetches the include paths for both pybind11 and Python headers. This assumes that pybind11 has been installed @@ -663,7 +663,7 @@ building the module: .. code-block:: bash - $ c++ -O3 -Wall -shared -std=c++11 -undefined dynamic_lookup $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix) + $ c++ -O3 -Wall -shared -std=c++14 -undefined dynamic_lookup $(python3 -m pybind11 --includes) example.cpp -o example$(python3-config --extension-suffix) For quick tests, the command line tool can also produce the full set of flags for you, based on ``python-config``: diff --git a/include/pybind11/detail/argument_vector.h b/include/pybind11/detail/argument_vector.h index 6e2c2ec481..41da4872d7 100644 --- a/include/pybind11/detail/argument_vector.h +++ b/include/pybind11/detail/argument_vector.h @@ -31,7 +31,7 @@ PYBIND11_WARNING_DISABLE_MSVC(4127) PYBIND11_NAMESPACE_BEGIN(detail) // Shared implementation utility for our small_vector-like containers. -// We support C++11 and C++14, so we cannot use +// We support C++14, so we cannot use // std::variant. Union with the tag packed next to the inline // array's size is smaller anyway, allowing 1 extra handle of // inline storage for free. Compare the layouts (1 line per diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 740001db78..1e26d180f7 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -72,6 +72,10 @@ # endif #endif +#if !defined(PYBIND11_CPP14) +# error "pybind11 requires C++14 or newer" +#endif + // These PYBIND11_HAS_... macros are consolidated in pybind11/detail/common.h // to simplify backward compatibility handling for users (e.g., via #ifdef checks): #define PYBIND11_HAS_TYPE_CASTER_STD_FUNCTION_SPECIALIZATIONS 1 @@ -109,10 +113,8 @@ // Compiler version assertions #if defined(__INTEL_COMPILER) -# if __INTEL_COMPILER < 1800 -# error pybind11 requires Intel C++ compiler v18 or newer -# elif __INTEL_COMPILER < 1900 && defined(PYBIND11_CPP14) -# error pybind11 supports only C++11 with Intel C++ compiler v18. Use v19 or newer for C++14. +# if __INTEL_COMPILER < 1900 +# error pybind11 requires Intel C++ compiler v19 or newer # endif /* The following pragma cannot be pop'ed: https://community.intel.com/t5/Intel-C-Compiler/Inline-and-no-inline-warning/td-p/1216764 */ @@ -179,10 +181,8 @@ // For unknown reasons all PYBIND11_DEPRECATED member trigger a warning when declared // whether it is used or not # define PYBIND11_DEPRECATED(reason) -#elif defined(PYBIND11_CPP14) -# define PYBIND11_DEPRECATED(reason) [[deprecated(reason)]] #else -# define PYBIND11_DEPRECATED(reason) __attribute__((deprecated(reason))) +# define PYBIND11_DEPRECATED(reason) [[deprecated(reason)]] #endif #if defined(PYBIND11_CPP17) @@ -203,9 +203,8 @@ # if defined(PYBIND11_CPP17) && __has_include() # define PYBIND11_HAS_OPTIONAL 1 # endif -// std::experimental::optional (but not allowed in c++11 mode) -# if defined(PYBIND11_CPP14) && (__has_include() && \ - !__has_include()) +// std::experimental::optional +# if __has_include() && !__has_include() # define PYBIND11_HAS_EXP_OPTIONAL 1 # endif // std::variant @@ -706,22 +705,11 @@ static_assert(std::is_standard_layout::value, template struct always_false : std::false_type {}; -/// from __cpp_future__ import (convenient aliases from C++14/17) -#if defined(PYBIND11_CPP14) +/// Convenient aliases from C++14 using std::conditional_t; using std::enable_if_t; using std::remove_cv_t; using std::remove_reference_t; -#else -template -using enable_if_t = typename std::enable_if::type; -template -using conditional_t = typename std::conditional::type; -template -using remove_cv_t = typename std::remove_cv::type; -template -using remove_reference_t = typename std::remove_reference::type; -#endif #if defined(PYBIND11_CPP20) && defined(__cpp_lib_remove_cvref) using std::remove_cvref; @@ -740,56 +728,8 @@ template using is_same_ignoring_cvref = std::is_same, U>; /// Index sequences -#if defined(PYBIND11_CPP14) using std::index_sequence; using std::make_index_sequence; -#else -template -struct index_sequence {}; -// Comments about the algorithm below. -// -// Credit: This is based on an algorithm by taocpp here: -// https://github.com/taocpp/sequences/blob/main/include/tao/seq/make_integer_sequence.hpp -// but significantly simplified. -// -// We build up a sequence S by repeatedly doubling its length and sometimes adding 1 to the end. -// E.g. if the current S is 0...3, then we either go to 0...7 or 0...8 on the next pass. -// The goal is to end with S = 0...N-1. -// The key insight is that the times we need to add an additional digit to S correspond -// exactly to the 1's in the binary representation of the number N. -// -// Invariants: -// - digit is a power of 2 -// - N_digit_is_1 is whether N's binary representation has a 1 in that digit's position. -// - end <= N -// - S is 0...end-1. -// - if digit > 0, end * digit * 2 <= N < (end+1) * digit * 2 -// -// The process starts with digit > N, end = 0, and S is empty. -// The process concludes with digit=0, in which case, end == N and S is 0...N-1. - -template // N_digit_is_1=false -struct make_index_sequence_impl - : make_index_sequence_impl { -}; -template -struct make_index_sequence_impl - : make_index_sequence_impl {}; -template -struct make_index_sequence_impl<0, false, N, end, S...> { - using type = index_sequence; -}; -constexpr size_t next_power_of_2(size_t N) { return N == 0 ? 1 : next_power_of_2(N >> 1) << 1; } -template -using make_index_sequence = - typename make_index_sequence_impl::type; -#endif /// Make an index sequence of the indices of true arguments template @@ -1277,29 +1217,18 @@ struct overload_cast_impl { }; PYBIND11_NAMESPACE_END(detail) -// overload_cast requires variable templates: C++14 -#if defined(PYBIND11_CPP14) -# define PYBIND11_OVERLOAD_CAST 1 +#define PYBIND11_OVERLOAD_CAST 1 /// Syntax sugar for resolving overloaded function pointers: /// - regular: static_cast(&Class::func) /// - sweet: overload_cast(&Class::func) template static constexpr detail::overload_cast_impl overload_cast{}; -#endif /// Const member function selector for overload_cast /// - regular: static_cast(&Class::func) /// - sweet: overload_cast(&Class::func, const_) static constexpr auto const_ = std::true_type{}; -#if !defined(PYBIND11_CPP14) // no overload_cast: providing something that static_assert-fails: -template -struct overload_cast { - static_assert(detail::deferred_t::value, - "pybind11::overload_cast<...> requires compiling in C++14 mode"); -}; -#endif // overload_cast - PYBIND11_NAMESPACE_BEGIN(detail) // Adaptor for converting arbitrary container arguments into a vector; implicitly convertible from diff --git a/include/pybind11/detail/init.h b/include/pybind11/detail/init.h index a1083f8457..ae2f66bd62 100644 --- a/include/pybind11/detail/init.h +++ b/include/pybind11/detail/init.h @@ -388,17 +388,9 @@ struct factory { // instance, or the alias needs to be constructible from a `Class &&` argument. template void execute(Class &cl, const Extra &...extra) && { -#if defined(PYBIND11_CPP14) cl.def( "__init__", - [func = std::move(class_factory)] -#else - auto &func = class_factory; - cl.def( - "__init__", - [func] -#endif - (value_and_holder &v_h, Args... args) { + [func = std::move(class_factory)](value_and_holder &v_h, Args... args) { construct( v_h, func(std::forward(args)...), Py_TYPE(v_h.inst) != v_h.type->type); }, @@ -435,18 +427,10 @@ struct factory { static_assert(Class::has_alias, "The two-argument version of `py::init()` can " "only be used if the class has an alias"); -#if defined(PYBIND11_CPP14) - cl.def( - "__init__", - [class_func = std::move(class_factory), alias_func = std::move(alias_factory)] -#else - auto &class_func = class_factory; - auto &alias_func = alias_factory; cl.def( "__init__", - [class_func, alias_func] -#endif - (value_and_holder &v_h, CArgs... args) { + [class_func = std::move(class_factory), + alias_func = std::move(alias_factory)](value_and_holder &v_h, CArgs... args) { if (Py_TYPE(v_h.inst) == v_h.type->type) { // If the instance type equals the registered type we don't have inheritance, // so don't need the alias and can construct using the class function: @@ -524,17 +508,9 @@ struct pickle_factory { void execute(Class &cl, const Extra &...extra) && { cl.def("__getstate__", std::move(get), pos_only()); -#if defined(PYBIND11_CPP14) - cl.def( - "__setstate__", - [func = std::move(set)] -#else - auto &func = set; cl.def( "__setstate__", - [func] -#endif - (value_and_holder &v_h, ArgState state) { + [func = std::move(set)](value_and_holder &v_h, ArgState state) { setstate( v_h, func(std::forward(state)), Py_TYPE(v_h.inst) != v_h.type->type); }, diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index eebb130694..75f92895ed 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1,5 +1,5 @@ /* - pybind11/pybind11.h: Main header file of the C++11 python + pybind11/pybind11.h: Main header file of the C++14 python binding generator library Copyright (c) 2016 Wenzel Jakob diff --git a/pybind11/setup_helpers.py b/pybind11/setup_helpers.py index 66a3eec0d4..ea92182c5f 100644 --- a/pybind11/setup_helpers.py +++ b/pybind11/setup_helpers.py @@ -1,5 +1,5 @@ """ -This module provides helpers for C++11+ projects using pybind11. +This module provides helpers for C++14+ projects using pybind11. LICENSE: @@ -88,7 +88,7 @@ class Pybind11Extension(_Extension): """ - Build a C++11+ Extension module with pybind11. This automatically adds the + Build a C++14+ Extension module with pybind11. This automatically adds the recommended flags when you init the extension and assumes C++ sources - you can further modify the options yourself. @@ -120,7 +120,7 @@ def _add_ldflags(self, flags: list[str]) -> None: self.extra_link_args[:0] = flags def __init__(self, *args: Any, **kwargs: Any) -> None: - self._cxx_level = 0 + self._cxx_level: str | int | None = 0 cxx_std = kwargs.pop("cxx_std", 0) if "language" not in kwargs: @@ -158,7 +158,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self._add_cflags(cflags) @property - def cxx_std(self) -> int: + def cxx_std(self) -> str | int | None: """ The CXX standard level. If set, will add the required flags. If left at 0, it will trigger an automatic search when pybind11's build_ext is @@ -169,16 +169,16 @@ def cxx_std(self) -> int: return self._cxx_level @cxx_std.setter - def cxx_std(self, level: int) -> None: + def cxx_std(self, level: str | int | None) -> None: + if isinstance(level, int) and level != 0 and level < 14: + msg = "pybind11 requires C++14 or newer" + raise ValueError(msg) + if self._cxx_level: warnings.warn( "You cannot safely change the cxx_level after setting it!", stacklevel=2 ) - # MSVC only has 14 and later modes, so force a valid flag here. - if WIN and level == 11: - level = 14 - self._cxx_level = level if not level: @@ -194,7 +194,9 @@ def cxx_std(self, level: int) -> None: # setting for general use. However, never set higher than the # current macOS version! current_macos = tuple(int(x) for x in platform.mac_ver()[0].split(".")[:2]) - desired_macos = (10, 9) if level < 17 else (10, 14) + desired_macos = ( + (10, 9) if isinstance(level, int) and level < 17 else (10, 14) + ) macos_string = ".".join(str(x) for x in min(current_macos, desired_macos)) macosx_min = f"-mmacosx-version-min={macos_string}" cflags += [macosx_min] @@ -247,19 +249,19 @@ def has_flag(compiler: Any, flag: str) -> bool: @lru_cache def auto_cpp_level(compiler: Any) -> str | int: """ - Return the max supported C++ std level (17, 14, or 11). Returns latest on Windows. + Return the max supported C++ std level (17 or 14). Returns latest on Windows. """ if WIN: return "latest" - levels = [17, 14, 11] + levels = [17, 14] for level in levels: if has_flag(compiler, STD_TMPL.format(level)): return level - msg = "Unsupported compiler -- at least C++11 support is needed!" + msg = "Unsupported compiler -- at least C++14 support is needed!" raise RuntimeError(msg) diff --git a/tests/extra_setuptools/test_setuphelper.py b/tests/extra_setuptools/test_setuphelper.py index 22c0c76f5d..b0a9128c8b 100644 --- a/tests/extra_setuptools/test_setuphelper.py +++ b/tests/extra_setuptools/test_setuphelper.py @@ -13,7 +13,7 @@ @pytest.mark.parametrize("parallel", [False, True]) -@pytest.mark.parametrize("std", [11, 0]) +@pytest.mark.parametrize("std", [14, 0]) def test_simple_setup_py(monkeypatch, tmpdir, parallel, std): monkeypatch.chdir(tmpdir) monkeypatch.syspath_prepend(MAIN_DIR) @@ -110,6 +110,14 @@ def test_simple_setup_py(monkeypatch, tmpdir, parallel, std): ) +def test_cxx_std_too_old(monkeypatch): + monkeypatch.syspath_prepend(MAIN_DIR) + from pybind11.setup_helpers import Pybind11Extension + + with pytest.raises(ValueError, match=r"pybind11 requires C\+\+14 or newer"): + Pybind11Extension("example", ["example.cpp"], cxx_std=11) + + def test_intree_extensions(monkeypatch, tmpdir): monkeypatch.syspath_prepend(MAIN_DIR) diff --git a/tests/pybind11_tests.cpp b/tests/pybind11_tests.cpp index 5dacd7aeb6..d96ba56ff0 100644 --- a/tests/pybind11_tests.cpp +++ b/tests/pybind11_tests.cpp @@ -68,10 +68,8 @@ const char *cpp_std() { "C++20"; #elif defined(PYBIND11_CPP17) "C++17"; -#elif defined(PYBIND11_CPP14) - "C++14"; #else - "C++11"; + "C++14"; #endif } diff --git a/tests/test_cmake_build/installed_target/CMakeLists.txt b/tests/test_cmake_build/installed_target/CMakeLists.txt index a66f8adda0..6f529c3190 100644 --- a/tests/test_cmake_build/installed_target/CMakeLists.txt +++ b/tests/test_cmake_build/installed_target/CMakeLists.txt @@ -7,6 +7,8 @@ message(STATUS "Found pybind11 v${pybind11_VERSION}: ${pybind11_INCLUDE_DIRS}") add_library(test_installed_target MODULE ../main.cpp) +# Verify that the installed interface promotes a consumer that initially requests C++11. +set_target_properties(test_installed_target PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED ON) target_link_libraries(test_installed_target PRIVATE pybind11::module) # If a pybind11 option is on, main.cpp checks that the matching macro reached this diff --git a/tests/test_cmake_build/subdirectory_target/CMakeLists.txt b/tests/test_cmake_build/subdirectory_target/CMakeLists.txt index 29b0234856..b193a2f117 100644 --- a/tests/test_cmake_build/subdirectory_target/CMakeLists.txt +++ b/tests/test_cmake_build/subdirectory_target/CMakeLists.txt @@ -13,6 +13,8 @@ add_subdirectory("${pybind11_SOURCE_DIR}" pybind11) add_library(test_subdirectory_target MODULE ../main.cpp) set_target_properties(test_subdirectory_target PROPERTIES OUTPUT_NAME test_cmake_build) +# Verify that pybind11's interface requirement promotes a consumer that initially requests C++11. +set_target_properties(test_subdirectory_target PROPERTIES CXX_STANDARD 11 CXX_STANDARD_REQUIRED ON) target_link_libraries(test_subdirectory_target PRIVATE pybind11::module) # If a pybind11 option is on, main.cpp checks that the matching macro reached this diff --git a/tests/test_cross_module_rtti/CMakeLists.txt b/tests/test_cross_module_rtti/CMakeLists.txt index c9b95bfba1..71451a4592 100644 --- a/tests/test_cross_module_rtti/CMakeLists.txt +++ b/tests/test_cross_module_rtti/CMakeLists.txt @@ -36,7 +36,7 @@ add_library(test_cross_module_rtti_lib::test_cross_module_rtti_lib ALIAS test_cross_module_rtti_lib) target_include_directories(test_cross_module_rtti_lib PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) target_include_directories(test_cross_module_rtti_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) -target_compile_features(test_cross_module_rtti_lib PUBLIC cxx_std_11) +target_compile_features(test_cross_module_rtti_lib PUBLIC cxx_std_14) generate_export_header(test_cross_module_rtti_lib) diff --git a/tools/pybind11Common.cmake b/tools/pybind11Common.cmake index d75fb67520..8aea516700 100644 --- a/tools/pybind11Common.cmake +++ b/tools/pybind11Common.cmake @@ -169,23 +169,21 @@ set_property( # Warn or error if old variable name used if(PYBIND11_CPP_STANDARD) string(REGEX MATCH [[..$]] VAL "${PYBIND11_CPP_STANDARD}") - if(CMAKE_CXX_STANDARD) + set(supported_standards 14 17 20) + if(NOT "${VAL}" IN_LIST supported_standards) + message(FATAL_ERROR "PYBIND11_CPP_STANDARD should be replaced with CMAKE_CXX_STANDARD " + "(${VAL} is below pybind11's C++14 minimum or is not supported)") + elseif(CMAKE_CXX_STANDARD) if(NOT CMAKE_CXX_STANDARD STREQUAL VAL) message(WARNING "CMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} does not match " "PYBIND11_CPP_STANDARD=${PYBIND11_CPP_STANDARD}, " "please remove PYBIND11_CPP_STANDARD from your cache") endif() else() - set(supported_standards 11 14 17 20) - if("${VAL}" IN_LIST supported_standards) - message(WARNING "USE -DCMAKE_CXX_STANDARD=${VAL} instead of PYBIND11_CPP_STANDARD") - set(CMAKE_CXX_STANDARD - ${VAL} - CACHE STRING "From PYBIND11_CPP_STANDARD") - else() - message(FATAL_ERROR "PYBIND11_CPP_STANDARD should be replaced with CMAKE_CXX_STANDARD " - "(last two chars: ${VAL} not understood as a valid CXX std)") - endif() + message(WARNING "USE -DCMAKE_CXX_STANDARD=${VAL} instead of PYBIND11_CPP_STANDARD") + set(CMAKE_CXX_STANDARD + ${VAL} + CACHE STRING "From PYBIND11_CPP_STANDARD") endif() endif() From 8ffb5d3eabb7204d95831fb82294d0cdd811b957 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Wed, 19 Aug 2026 22:53:46 -0400 Subject: [PATCH 2/3] refactor: simplify internals with C++14 features - ReadableFunctionSignature: use return type deduction, removing the PYBIND11_READABLE_FUNCTION_SIGNATURE_EXPR macro duplication. - constexpr_first/constexpr_last: use relaxed constexpr loops, removing the constexpr_impl recursion helpers. - constexpr_sum: one loop implementation, removing the __cpp_fold_expressions branch. - Use std::make_unique where the type is unchanged. - Replace 'typename std::X<...>::type' with the C++14 'std::X_t<...>' alias templates. Assisted-by: ClaudeCode:claude-fable-5 --- include/pybind11/attr.h | 12 ++-- include/pybind11/cast.h | 64 +++++++++---------- include/pybind11/detail/common.h | 50 ++++++--------- include/pybind11/detail/internals.h | 4 +- include/pybind11/detail/struct_smart_holder.h | 6 +- include/pybind11/detail/type_caster_base.h | 38 +++++------ include/pybind11/eigen/tensor.h | 4 +- include/pybind11/functional.h | 3 +- include/pybind11/native_enum.h | 2 +- include/pybind11/numpy.h | 25 ++++---- include/pybind11/pybind11.h | 34 ++++------ include/pybind11/stl_bind.h | 2 +- 12 files changed, 108 insertions(+), 136 deletions(-) diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index d337595a0b..5daef10d73 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -677,28 +677,24 @@ struct process_attributes { PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(r); PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(r); using expander = int[]; - (void) expander{ - 0, ((void) process_attribute::type>::init(args, r), 0)...}; + (void) expander{0, ((void) process_attribute>::init(args, r), 0)...}; } static void init(const Args &...args, type_record *r) { PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(r); PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(r); using expander = int[]; - (void) expander{0, - (process_attribute::type>::init(args, r), 0)...}; + (void) expander{0, (process_attribute>::init(args, r), 0)...}; } static void precall(function_call &call) { PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(call); using expander = int[]; - (void) expander{0, - (process_attribute::type>::precall(call), 0)...}; + (void) expander{0, (process_attribute>::precall(call), 0)...}; } static void postcall(function_call &call, handle fn_ret) { PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(call, fn_ret); PYBIND11_WORKAROUND_INCORRECT_GCC_UNUSED_BUT_SET_PARAMETER(fn_ret); using expander = int[]; - (void) expander{ - 0, (process_attribute::type>::postcall(call, fn_ret), 0)...}; + (void) expander{0, (process_attribute>::postcall(call, fn_ret), 0)...}; } }; diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 1d857a0ed5..a6bf8389be 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -49,17 +49,18 @@ typename make_caster::template cast_op_type cast_op(make_caster &caster return caster.operator result_t(); } template -typename make_caster::template cast_op_type::type> +typename make_caster::template cast_op_type> cast_op(make_caster &&caster) { - using result_t = typename make_caster::template cast_op_type< - typename std::add_rvalue_reference::type>; // See PR #4893 + using result_t = + typename make_caster::template cast_op_type>; // See PR + // #4893 return std::move(caster).operator result_t(); } template class type_caster_enum_type { private: - using Underlying = typename std::underlying_type::type; + using Underlying = std::underlying_type_t; public: static constexpr auto name = const_name(); @@ -167,11 +168,10 @@ class type_caster> { using reference_t = type &; using subcaster_cast_op_type = typename caster_t::template cast_op_type; - static_assert( - std::is_same::type &, subcaster_cast_op_type>::value - || std::is_same::value, - "std::reference_wrapper caster requires T to have a caster with an " - "`operator T &()` or `operator const T &()`"); + static_assert(std::is_same &, subcaster_cast_op_type>::value + || std::is_same::value, + "std::reference_wrapper caster requires T to have a caster with an " + "`operator T &()` or `operator const T &()`"); public: bool load(handle src, bool convert) { return subcaster.load(src, convert); } @@ -230,9 +230,8 @@ using is_std_char_type = any_of, /* std::string */ template struct type_caster::value && !is_std_char_type::value>> { using _py_type_0 = conditional_t; - using _py_type_1 = conditional_t::value, - _py_type_0, - typename std::make_unsigned<_py_type_0>::type>; + using _py_type_1 + = conditional_t::value, _py_type_0, std::make_unsigned_t<_py_type_0>>; using py_type = conditional_t::value, double, _py_type_1>; public: @@ -310,39 +309,39 @@ struct type_caster::value && !is_std_char_t } template - static typename std::enable_if::value, handle>::type + static std::enable_if_t::value, handle> cast(U src, return_value_policy /* policy */, handle /* parent */) { return PyFloat_FromDouble((double) src); } template - static typename std::enable_if::value && std::is_signed::value - && (sizeof(U) <= sizeof(long)), - handle>::type + static std::enable_if_t::value && std::is_signed::value + && (sizeof(U) <= sizeof(long)), + handle> cast(U src, return_value_policy /* policy */, handle /* parent */) { return PYBIND11_LONG_FROM_SIGNED((long) src); } template - static typename std::enable_if::value && std::is_unsigned::value - && (sizeof(U) <= sizeof(unsigned long)), - handle>::type + static std::enable_if_t::value && std::is_unsigned::value + && (sizeof(U) <= sizeof(unsigned long)), + handle> cast(U src, return_value_policy /* policy */, handle /* parent */) { return PYBIND11_LONG_FROM_UNSIGNED((unsigned long) src); } template - static typename std::enable_if::value && std::is_signed::value - && (sizeof(U) > sizeof(long)), - handle>::type + static std::enable_if_t::value && std::is_signed::value + && (sizeof(U) > sizeof(long)), + handle> cast(U src, return_value_policy /* policy */, handle /* parent */) { return PyLong_FromLongLong((long long) src); } template - static typename std::enable_if::value && std::is_unsigned::value - && (sizeof(U) > sizeof(unsigned long)), - handle>::type + static std::enable_if_t::value && std::is_unsigned::value + && (sizeof(U) > sizeof(unsigned long)), + handle> cast(U src, return_value_policy /* policy */, handle /* parent */) { return PyLong_FromUnsignedLongLong((unsigned long long) src); } @@ -1269,13 +1268,12 @@ struct move_only_holder_caster< } template - using cast_op_type - = conditional_t::type, - const std::unique_ptr &>::value - || std::is_same::type, - const std::unique_ptr &>::value, - const std::unique_ptr &, - std::unique_ptr>; + using cast_op_type = conditional_t< + std::is_same, const std::unique_ptr &>::value + || std::is_same, + const std::unique_ptr &>::value, + const std::unique_ptr &, + std::unique_ptr>; explicit operator std::unique_ptr() { if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) { @@ -1738,7 +1736,7 @@ template ::value, int> = object cast(T &&value, return_value_policy policy = return_value_policy::automatic_reference, handle parent = handle()) { - using no_ref_T = typename std::remove_reference::type; + using no_ref_T = std::remove_reference_t; if (policy == return_value_policy::automatic) { policy = std::is_pointer::value ? return_value_policy::take_ownership : std::is_lvalue_reference::value ? return_value_policy::copy diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 1e26d180f7..e52871284d 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -857,45 +857,38 @@ template struct type_list {}; /// Compile-time integer sum -#ifdef __cpp_fold_expressions template constexpr size_t constexpr_sum(Ts... ns) { - return (0 + ... + size_t{ns}); -} -#else -constexpr size_t constexpr_sum() { return 0; } -template -constexpr size_t constexpr_sum(T n, Ts... ns) { - return size_t{n} + constexpr_sum(ns...); -} -#endif - -PYBIND11_NAMESPACE_BEGIN(constexpr_impl) -/// Implementation details for constexpr functions -constexpr int first(int i) { return i; } -template -constexpr int first(int i, T v, Ts... vs) { - return v ? i : first(i + 1, vs...); -} - -constexpr int last(int /*i*/, int result) { return result; } -template -constexpr int last(int i, int result, T v, Ts... vs) { - return last(i + 1, v ? i : result, vs...); + size_t result = 0; + for (size_t n : {size_t{0}, size_t{ns}...}) { + result += n; + } + return result; } -PYBIND11_NAMESPACE_END(constexpr_impl) /// Return the index of the first type in Ts which satisfies Predicate. /// Returns sizeof...(Ts) if none match. template