Skip to content

Commit fe1266e

Browse files
jagermanwjakob
authored andcommitted
Fix char & arguments being non-bindable
This changes the caster to return a reference to a (new) local `CharT` type caster member so that binding lvalue-reference char arguments works (currently it results in a compilation failure). Fixes #1116
1 parent 86e2ad4 commit fe1266e

File tree

3 files changed

+14
-6
lines changed

3 files changed

+14
-6
lines changed

include/pybind11/cast.h

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,6 +1216,7 @@ template <typename CharT> struct type_caster<CharT, enable_if_t<is_std_char_type
12161216
using StringCaster = type_caster<StringType>;
12171217
StringCaster str_caster;
12181218
bool none = false;
1219+
CharT one_char = 0;
12191220
public:
12201221
bool load(handle src, bool convert) {
12211222
if (!src) return false;
@@ -1243,7 +1244,7 @@ template <typename CharT> struct type_caster<CharT, enable_if_t<is_std_char_type
12431244
}
12441245

12451246
operator CharT*() { return none ? nullptr : const_cast<CharT *>(static_cast<StringType &>(str_caster).c_str()); }
1246-
operator CharT() {
1247+
operator CharT&() {
12471248
if (none)
12481249
throw value_error("Cannot convert None to a character");
12491250

@@ -1267,7 +1268,8 @@ template <typename CharT> struct type_caster<CharT, enable_if_t<is_std_char_type
12671268
if (char0_bytes == str_len) {
12681269
// If we have a 128-255 value, we can decode it into a single char:
12691270
if (char0_bytes == 2 && (v0 & 0xFC) == 0xC0) { // 0x110000xx 0x10xxxxxx
1270-
return static_cast<CharT>(((v0 & 3) << 6) + (static_cast<unsigned char>(value[1]) & 0x3F));
1271+
one_char = static_cast<CharT>(((v0 & 3) << 6) + (static_cast<unsigned char>(value[1]) & 0x3F));
1272+
return one_char;
12711273
}
12721274
// Otherwise we have a single character, but it's > U+00FF
12731275
throw value_error("Character code point not in range(0x100)");
@@ -1278,19 +1280,20 @@ template <typename CharT> struct type_caster<CharT, enable_if_t<is_std_char_type
12781280
// surrogate pair with total length 2 instantly indicates a range error (but not a "your
12791281
// string was too long" error).
12801282
else if (StringCaster::UTF_N == 16 && str_len == 2) {
1281-
char16_t v0 = static_cast<char16_t>(value[0]);
1282-
if (v0 >= 0xD800 && v0 < 0xE000)
1283+
one_char = static_cast<CharT>(value[0]);
1284+
if (one_char >= 0xD800 && one_char < 0xE000)
12831285
throw value_error("Character code point not in range(0x10000)");
12841286
}
12851287

12861288
if (str_len != 1)
12871289
throw value_error("Expected a character, but multi-character string found");
12881290

1289-
return value[0];
1291+
one_char = value[0];
1292+
return one_char;
12901293
}
12911294

12921295
static PYBIND11_DESCR name() { return type_descr(_(PYBIND11_STRING_NAME)); }
1293-
template <typename _T> using cast_op_type = remove_reference_t<pybind11::detail::cast_op_type<_T>>;
1296+
template <typename _T> using cast_op_type = pybind11::detail::cast_op_type<_T>;
12941297
};
12951298

12961299
// Base implementation for std::tuple and std::pair

tests/test_builtin_casters.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ TEST_SUBMODULE(builtin_casters, m) {
5050
// test_single_char_arguments
5151
m.attr("wchar_size") = py::cast(sizeof(wchar_t));
5252
m.def("ord_char", [](char c) -> int { return static_cast<unsigned char>(c); });
53+
m.def("ord_char_lv", [](char &c) -> int { return static_cast<unsigned char>(c); });
5354
m.def("ord_char16", [](char16_t c) -> uint16_t { return c; });
55+
m.def("ord_char16_lv", [](char16_t &c) -> uint16_t { return c; });
5456
m.def("ord_char32", [](char32_t c) -> uint32_t { return c; });
5557
m.def("ord_wchar", [](wchar_t c) -> int { return c; });
5658

tests/test_builtin_casters.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def toobig_message(r):
4444
toolong_message = "Expected a character, but multi-character string found"
4545

4646
assert m.ord_char(u'a') == 0x61 # simple ASCII
47+
assert m.ord_char_lv(u'b') == 0x62
4748
assert m.ord_char(u'é') == 0xE9 # requires 2 bytes in utf-8, but can be stuffed in a char
4849
with pytest.raises(ValueError) as excinfo:
4950
assert m.ord_char(u'Ā') == 0x100 # requires 2 bytes, doesn't fit in a char
@@ -54,9 +55,11 @@ def toobig_message(r):
5455

5556
assert m.ord_char16(u'a') == 0x61
5657
assert m.ord_char16(u'é') == 0xE9
58+
assert m.ord_char16_lv(u'ê') == 0xEA
5759
assert m.ord_char16(u'Ā') == 0x100
5860
assert m.ord_char16(u'‽') == 0x203d
5961
assert m.ord_char16(u'♥') == 0x2665
62+
assert m.ord_char16_lv(u'♡') == 0x2661
6063
with pytest.raises(ValueError) as excinfo:
6164
assert m.ord_char16(u'🎂') == 0x1F382 # requires surrogate pair
6265
assert str(excinfo.value) == toobig_message(0x10000)

0 commit comments

Comments
 (0)