diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 5bc4605a7..fc2612b6e 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -693,9 +693,9 @@ struct type_caster, enable_if_t= 3 - return false; - // The below is a guaranteed failure in Python 3 when PyUnicode_Check returns false + return load_bytes(load_src); #else + // The below is a guaranteed failure in Python 3 when PyUnicode_Check returns false if (!PYBIND11_BYTES_CHECK(load_src.ptr())) return false; temp = reinterpret_steal(PyUnicode_FromObject(load_src.ptr())); @@ -740,6 +740,28 @@ private: return PyUnicode_Decode(buffer, nbytes, UTF_N == 8 ? "utf-8" : UTF_N == 16 ? "utf-16" : "utf-32", nullptr); #endif } + +#if PY_MAJOR_VERSION >= 3 + // In Python 3, when loading into a std::string or char*, accept a bytes object as-is (i.e. + // without any encoding/decoding attempt). For other C++ char sizes this is a no-op. Python 2, + // which supports loading a unicode from a str, doesn't take this path. + template + bool load_bytes(enable_if_t src) { + if (PYBIND11_BYTES_CHECK(src.ptr())) { + // We were passed a Python 3 raw bytes; accept it into a std::string or char* + // without any encoding attempt. + const char *bytes = PYBIND11_BYTES_AS_STRING(src.ptr()); + if (bytes) { + value = StringType(bytes, (size_t) PYBIND11_BYTES_SIZE(src.ptr())); + return true; + } + } + + return false; + } + template + bool load_bytes(enable_if_t) { return false; } +#endif }; // Type caster for C-style strings. We basically use a std::string type caster, but also add the diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp index 6f2080994..18aa87a6f 100644 --- a/tests/test_python_types.cpp +++ b/tests/test_python_types.cpp @@ -473,6 +473,9 @@ test_initializer python_types([](py::module &m) { m.def("ord_char32", [](char32_t c) -> uint32_t { return c; }); m.def("ord_wchar", [](wchar_t c) -> int { return c; }); + m.def("strlen", [](char *s) { return strlen(s); }); + m.def("string_length", [](std::string s) { return s.length(); }); + m.def("return_none_string", []() -> std::string * { return nullptr; }); m.def("return_none_char", []() -> const char * { return nullptr; }); m.def("return_none_bool", []() -> bool * { return nullptr; }); diff --git a/tests/test_python_types.py b/tests/test_python_types.py index cf8c1476b..9849bc849 100644 --- a/tests/test_python_types.py +++ b/tests/test_python_types.py @@ -511,6 +511,20 @@ def test_single_char_arguments(): assert str(excinfo.value) == toolong_message +def test_bytes_to_string(): + """Tests the ability to pass bytes to C++ string-accepting functions. Note that this is + one-way: the only way to return bytes to Python is via the pybind11::bytes class.""" + # Issue #816 + from pybind11_tests import strlen, string_length + import sys + byte = bytes if sys.version_info[0] < 3 else str + + assert strlen(byte("hi")) == 2 + assert string_length(byte("world")) == 5 + assert string_length(byte("a\x00b")) == 3 + assert strlen(byte("a\x00b")) == 1 # C-string limitation + + def test_builtins_cast_return_none(): """Casters produced with PYBIND11_TYPE_CASTER() should convert nullptr to None""" import pybind11_tests as m