diff --git a/include/pybind11/detail/descr.h b/include/pybind11/detail/descr.h index 14f9223a5..0f93e06b2 100644 --- a/include/pybind11/detail/descr.h +++ b/include/pybind11/detail/descr.h @@ -59,6 +59,7 @@ constexpr descr<0> const_name(char const(&)[1]) { return {}; } template struct int_to_str : int_to_str { }; template struct int_to_str<0, Digits...> { + // WARNING: This only works with C++17 or higher. static constexpr auto digits = descr(('0' + Digits)...); }; @@ -84,9 +85,12 @@ auto constexpr const_name() -> remove_cv_t constexpr descr<1, Type> const_name() { return {'%'}; } -// The "_" might be defined as a macro - don't define it if so. -// Repeating the const_name code to avoid introducing a #define. +// If "_" is defined as a macro, py::detail::_ cannot be provided. +// It is therefore best to use py::detail::const_name universally. +// This block is for backward compatibility only. +// (The const_name code is repeated to avoid introducing a "_" #define ourselves.) #ifndef _ +#define PYBIND11_DETAIL_UNDERSCORE_BACKWARD_COMPATIBILITY template constexpr descr _(char const(&text)[N]) { return const_name(text); } template @@ -107,7 +111,7 @@ auto constexpr _() -> remove_cv_t::dig return const_name(); } template constexpr descr<1, Type> _() { return const_name(); } -#endif +#endif // #ifndef _ constexpr descr<0> concat() { return {}; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8b6d0a9ac..c2d7dc5ac 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -101,6 +101,7 @@ set(PYBIND11_TEST_FILES test_callbacks.cpp test_chrono.cpp test_class.cpp + test_const_name.cpp test_constants_and_functions.cpp test_copy_move.cpp test_custom_type_casters.cpp diff --git a/tests/test_const_name.cpp b/tests/test_const_name.cpp new file mode 100644 index 000000000..5cb3d16c1 --- /dev/null +++ b/tests/test_const_name.cpp @@ -0,0 +1,70 @@ +// Copyright (c) 2021 The Pybind Development Team. +// All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#include "pybind11_tests.h" + +#if defined(_MSC_VER) && _MSC_VER < 1910 + +// MSVC 2015 fails in bizarre ways. +# define PYBIND11_SKIP_TEST_CONST_NAME + +#else // Only test with MSVC 2017 or newer. + +// IUT = Implementation Under Test +# define CONST_NAME_TESTS(TEST_FUNC, IUT) \ + std::string TEST_FUNC(int selector) { \ + switch (selector) { \ + case 0: \ + return IUT("").text; \ + case 1: \ + return IUT("A").text; \ + case 2: \ + return IUT("Bd").text; \ + case 3: \ + return IUT("Cef").text; \ + case 4: \ + return IUT().text; /*NOLINT(bugprone-macro-parentheses)*/ \ + case 5: \ + return IUT().text; /*NOLINT(bugprone-macro-parentheses)*/ \ + case 6: \ + return IUT("T1", "T2").text; /*NOLINT(bugprone-macro-parentheses)*/ \ + case 7: \ + return IUT("U1", "U2").text; /*NOLINT(bugprone-macro-parentheses)*/ \ + case 8: \ + /*NOLINTNEXTLINE(bugprone-macro-parentheses)*/ \ + return IUT(IUT("D1"), IUT("D2")).text; \ + case 9: \ + /*NOLINTNEXTLINE(bugprone-macro-parentheses)*/ \ + return IUT(IUT("E1"), IUT("E2")).text; \ + case 10: \ + return IUT("KeepAtEnd").text; \ + default: \ + break; \ + } \ + throw std::runtime_error("Invalid selector value."); \ + } + +CONST_NAME_TESTS(const_name_tests, py::detail::const_name) + +# ifdef PYBIND11_DETAIL_UNDERSCORE_BACKWARD_COMPATIBILITY +CONST_NAME_TESTS(underscore_tests, py::detail::_) +# endif + +#endif // MSVC >= 2017 + +TEST_SUBMODULE(const_name, m) { +#ifdef PYBIND11_SKIP_TEST_CONST_NAME + m.attr("const_name_tests") = "PYBIND11_SKIP_TEST_CONST_NAME"; +#else + m.def("const_name_tests", const_name_tests); +#endif + +#ifdef PYBIND11_SKIP_TEST_CONST_NAME + m.attr("underscore_tests") = "PYBIND11_SKIP_TEST_CONST_NAME"; +#elif defined(PYBIND11_DETAIL_UNDERSCORE_BACKWARD_COMPATIBILITY) + m.def("underscore_tests", underscore_tests); +#else + m.attr("underscore_tests") = "PYBIND11_DETAIL_UNDERSCORE_BACKWARD_COMPATIBILITY not defined."; +#endif +} diff --git a/tests/test_const_name.py b/tests/test_const_name.py new file mode 100644 index 000000000..d4e45e5e9 --- /dev/null +++ b/tests/test_const_name.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +import pytest + +import env +from pybind11_tests import const_name as m + + +@pytest.mark.parametrize("func", (m.const_name_tests, m.underscore_tests)) +@pytest.mark.parametrize( + "selector, expected", + enumerate( + ( + "", + "A", + "Bd", + "Cef", + "%", + "%", + "T1", + "U2", + "D1", + "E2", + "KeepAtEnd", + ) + ), +) +def test_const_name(func, selector, expected): + if isinstance(func, type(u"") if env.PY2 else str): + pytest.skip(func) + text = func(selector) + assert text == expected diff --git a/tests/test_custom_type_casters.cpp b/tests/test_custom_type_casters.cpp index 8ad584de6..48613ee5a 100644 --- a/tests/test_custom_type_casters.cpp +++ b/tests/test_custom_type_casters.cpp @@ -19,7 +19,7 @@ namespace pybind11 { namespace detail { template <> struct type_caster { public: // Classic -#ifndef _ +#ifdef PYBIND11_DETAIL_UNDERSCORE_BACKWARD_COMPATIBILITY PYBIND11_TYPE_CASTER(ArgInspector1, _("ArgInspector1")); #else PYBIND11_TYPE_CASTER(ArgInspector1, const_name("ArgInspector1"));