diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 16a399ea0..cfd5df82c 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1216,6 +1216,7 @@ template struct type_caster; StringCaster str_caster; bool none = false; + CharT one_char = 0; public: bool load(handle src, bool convert) { if (!src) return false; @@ -1243,7 +1244,7 @@ public: } operator CharT*() { return none ? nullptr : const_cast(static_cast(str_caster).c_str()); } - operator CharT() { + operator CharT&() { if (none) throw value_error("Cannot convert None to a character"); @@ -1267,7 +1268,8 @@ public: if (char0_bytes == str_len) { // If we have a 128-255 value, we can decode it into a single char: if (char0_bytes == 2 && (v0 & 0xFC) == 0xC0) { // 0x110000xx 0x10xxxxxx - return static_cast(((v0 & 3) << 6) + (static_cast(value[1]) & 0x3F)); + one_char = static_cast(((v0 & 3) << 6) + (static_cast(value[1]) & 0x3F)); + return one_char; } // Otherwise we have a single character, but it's > U+00FF throw value_error("Character code point not in range(0x100)"); @@ -1278,19 +1280,20 @@ public: // surrogate pair with total length 2 instantly indicates a range error (but not a "your // string was too long" error). else if (StringCaster::UTF_N == 16 && str_len == 2) { - char16_t v0 = static_cast(value[0]); - if (v0 >= 0xD800 && v0 < 0xE000) + one_char = static_cast(value[0]); + if (one_char >= 0xD800 && one_char < 0xE000) throw value_error("Character code point not in range(0x10000)"); } if (str_len != 1) throw value_error("Expected a character, but multi-character string found"); - return value[0]; + one_char = value[0]; + return one_char; } static constexpr auto name = _(PYBIND11_STRING_NAME); - template using cast_op_type = remove_reference_t>; + template using cast_op_type = pybind11::detail::cast_op_type<_T>; }; // Base implementation for std::tuple and std::pair diff --git a/tests/test_builtin_casters.cpp b/tests/test_builtin_casters.cpp index b73e96ea5..e5413c2cc 100644 --- a/tests/test_builtin_casters.cpp +++ b/tests/test_builtin_casters.cpp @@ -50,7 +50,9 @@ TEST_SUBMODULE(builtin_casters, m) { // test_single_char_arguments m.attr("wchar_size") = py::cast(sizeof(wchar_t)); m.def("ord_char", [](char c) -> int { return static_cast(c); }); + m.def("ord_char_lv", [](char &c) -> int { return static_cast(c); }); m.def("ord_char16", [](char16_t c) -> uint16_t { return c; }); + m.def("ord_char16_lv", [](char16_t &c) -> uint16_t { return c; }); m.def("ord_char32", [](char32_t c) -> uint32_t { return c; }); m.def("ord_wchar", [](wchar_t c) -> int { return c; }); diff --git a/tests/test_builtin_casters.py b/tests/test_builtin_casters.py index bc094a381..2f311f152 100644 --- a/tests/test_builtin_casters.py +++ b/tests/test_builtin_casters.py @@ -44,6 +44,7 @@ def test_single_char_arguments(): toolong_message = "Expected a character, but multi-character string found" assert m.ord_char(u'a') == 0x61 # simple ASCII + assert m.ord_char_lv(u'b') == 0x62 assert m.ord_char(u'é') == 0xE9 # requires 2 bytes in utf-8, but can be stuffed in a char with pytest.raises(ValueError) as excinfo: assert m.ord_char(u'Ā') == 0x100 # requires 2 bytes, doesn't fit in a char @@ -54,9 +55,11 @@ def test_single_char_arguments(): assert m.ord_char16(u'a') == 0x61 assert m.ord_char16(u'é') == 0xE9 + assert m.ord_char16_lv(u'ê') == 0xEA assert m.ord_char16(u'Ā') == 0x100 assert m.ord_char16(u'‽') == 0x203d assert m.ord_char16(u'♥') == 0x2665 + assert m.ord_char16_lv(u'♡') == 0x2661 with pytest.raises(ValueError) as excinfo: assert m.ord_char16(u'🎂') == 0x1F382 # requires surrogate pair assert str(excinfo.value) == toobig_message(0x10000)