From f42af24a7d9259f41cdfad08f5f9d240b91ad16b Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Sun, 18 Jun 2017 20:32:22 -0400 Subject: [PATCH] Support std::string_view when compiled under C++17 --- docs/advanced/cast/overview.rst | 3 +++ docs/advanced/cast/strings.rst | 9 +++++++ include/pybind11/cast.h | 41 +++++++++++++++++++++++++----- tests/test_python_types.cpp | 15 +++++++++++ tests/test_python_types.py | 45 ++++++++++++++++++++++++++++++++- 5 files changed, 106 insertions(+), 7 deletions(-) diff --git a/docs/advanced/cast/overview.rst b/docs/advanced/cast/overview.rst index 49781dc75..2ac7d3009 100644 --- a/docs/advanced/cast/overview.rst +++ b/docs/advanced/cast/overview.rst @@ -116,6 +116,9 @@ as arguments and return values, refer to the section on binding :ref:`classes`. +------------------------------------+---------------------------+-------------------------------+ | ``std::wstring`` | STL dynamic wide string | :file:`pybind11/pybind11.h` | +------------------------------------+---------------------------+-------------------------------+ +| ``std::string_view``, | STL C++17 string views | :file:`pybind11/pybind11.h` | +| ``std::u16string_view``, etc. | | | ++------------------------------------+---------------------------+-------------------------------+ | ``std::pair`` | Pair of two custom types | :file:`pybind11/pybind11.h` | +------------------------------------+---------------------------+-------------------------------+ | ``std::tuple<...>`` | Arbitrary tuple of types | :file:`pybind11/pybind11.h` | diff --git a/docs/advanced/cast/strings.rst b/docs/advanced/cast/strings.rst index 0c994f6c4..864373e63 100644 --- a/docs/advanced/cast/strings.rst +++ b/docs/advanced/cast/strings.rst @@ -287,6 +287,15 @@ expressed as a single Unicode code point no way to capture them in a C++ character type. +C++17 string views +================== + +C++17 string views are automatically supported when compiling in C++17 mode. +They follow the same rules for encoding and decoding as the corresponding STL +string type (for example, a ``std::u16string_view`` argument will be passed +UTF-16-encoded data, and a returned ``std::string_view`` will be decoded as +UTF-8). + References ========== diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 78fb13c9b..fbca3c61b 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -18,6 +18,19 @@ #include #include +#if defined(PYBIND11_CPP17) +# if defined(__has_include) +# if __has_include() +# define PYBIND11_HAS_STRING_VIEW +# endif +# elif defined(_MSC_VER) +# define PYBIND11_HAS_STRING_VIEW +# endif +#endif +#ifdef PYBIND11_HAS_STRING_VIEW +#include +#endif + NAMESPACE_BEGIN(pybind11) NAMESPACE_BEGIN(detail) // Forward declarations: @@ -1003,10 +1016,11 @@ public: }; // Helper class for UTF-{8,16,32} C++ stl strings: -template -struct type_caster, enable_if_t::value>> { +template struct string_caster { + using CharT = typename StringType::value_type; + // Simplify life by being able to assume standard char sizes (the standard only guarantees - // minimums), but Python requires exact sizes + // minimums, but Python requires exact sizes) static_assert(!std::is_same::value || sizeof(CharT) == 1, "Unsupported char size != 1"); static_assert(!std::is_same::value || sizeof(CharT) == 2, "Unsupported char16_t size != 2"); static_assert(!std::is_same::value || sizeof(CharT) == 4, "Unsupported char32_t size != 4"); @@ -1015,8 +1029,6 @@ struct type_caster, enable_if_t; - bool load(handle src, bool) { #if PY_MAJOR_VERSION < 3 object temp; @@ -1050,11 +1062,16 @@ struct type_caster, enable_if_t 8) { buffer++; length--; } // Skip BOM for UTF-16/32 value = StringType(buffer, length); + + // If we're loading a string_view we need to keep the encoded Python object alive: + if (IsView) + view_into = std::move(utfNbytes); + return true; } static handle cast(const StringType &src, return_value_policy /* policy */, handle /* parent */) { - const char *buffer = reinterpret_cast(src.c_str()); + const char *buffer = reinterpret_cast(src.data()); ssize_t nbytes = ssize_t(src.size() * sizeof(CharT)); handle s = decode_utfN(buffer, nbytes); if (!s) throw error_already_set(); @@ -1064,6 +1081,8 @@ struct type_caster, enable_if_t) { return false; } }; +template +struct type_caster, enable_if_t::value>> + : string_caster> {}; + +#ifdef PYBIND11_HAS_STRING_VIEW +template +struct type_caster, enable_if_t::value>> + : string_caster, true> {}; +#endif + // Type caster for C-style strings. We basically use a std::string type caster, but also add the // ability to use None as a nullptr char* (which the string caster doesn't allow). template struct type_caster::value>> { diff --git a/tests/test_python_types.cpp b/tests/test_python_types.cpp index a3ed2895b..d130af6f1 100644 --- a/tests/test_python_types.cpp +++ b/tests/test_python_types.cpp @@ -555,6 +555,21 @@ test_initializer python_types([](py::module &m) { m.def("nodefer_none_optional", [](py::none) { return false; }); #endif +#ifdef PYBIND11_HAS_STRING_VIEW + m.attr("has_string_view") = true; + m.def("string_view_print", [](std::string_view s) { py::print(s, s.size()); }); + m.def("string_view16_print", [](std::u16string_view s) { py::print(s, s.size()); }); + m.def("string_view32_print", [](std::u32string_view s) { py::print(s, s.size()); }); + m.def("string_view_chars", [](std::string_view s) { py::list l; for (auto c : s) l.append((std::uint8_t) c); return l; }); + m.def("string_view16_chars", [](std::u16string_view s) { py::list l; for (auto c : s) l.append((int) c); return l; }); + m.def("string_view32_chars", [](std::u32string_view s) { py::list l; for (auto c : s) l.append((int) c); return l; }); + m.def("string_view_return", []() { return std::string_view(u8"utf8 secret \U0001f382"); }); + m.def("string_view16_return", []() { return std::u16string_view(u"utf16 secret \U0001f382"); }); + m.def("string_view32_return", []() { return std::u32string_view(U"utf32 secret \U0001f382"); }); +#else + m.attr("has_string_view") = false; +#endif + m.def("return_capsule_with_destructor", []() { py::print("creating capsule"); diff --git a/tests/test_python_types.py b/tests/test_python_types.py index ecd317e20..2af9432e4 100644 --- a/tests/test_python_types.py +++ b/tests/test_python_types.py @@ -2,7 +2,8 @@ import pytest import pybind11_tests -from pybind11_tests import ExamplePythonTypes, ConstructorStats, has_optional, has_exp_optional +from pybind11_tests import (ExamplePythonTypes, ConstructorStats, has_optional, has_exp_optional, + has_string_view) def test_repr(): @@ -558,6 +559,48 @@ def test_bytes_to_string(): assert string_length(u'💩'.encode("utf8")) == 4 +@pytest.mark.skipif(not has_string_view, reason='no ') +def test_string_view(capture): + """Tests support for C++17 string_view arguments and return values""" + from pybind11_tests import (string_view_print, string_view16_print, string_view32_print, + string_view_chars, string_view16_chars, string_view32_chars, + string_view_return, string_view16_return, string_view32_return) + + assert string_view_chars("Hi") == [72, 105] + assert string_view_chars("Hi 🎂") == [72, 105, 32, 0xf0, 0x9f, 0x8e, 0x82] + assert string_view16_chars("Hi 🎂") == [72, 105, 32, 0xd83c, 0xdf82] + assert string_view32_chars("Hi 🎂") == [72, 105, 32, 127874] + + assert string_view_return() == "utf8 secret 🎂" + assert string_view16_return() == "utf16 secret 🎂" + assert string_view32_return() == "utf32 secret 🎂" + + with capture: + string_view_print("Hi") + string_view_print("utf8 🎂") + string_view16_print("utf16 🎂") + string_view32_print("utf32 🎂") + + assert capture == """ + Hi 2 + utf8 🎂 9 + utf16 🎂 8 + utf32 🎂 7 + """ + + with capture: + string_view_print("Hi, ascii") + string_view_print("Hi, utf8 🎂") + string_view16_print("Hi, utf16 🎂") + string_view32_print("Hi, utf32 🎂") + assert capture == """ + Hi, ascii 9 + Hi, utf8 🎂 13 + Hi, utf16 🎂 12 + Hi, utf32 🎂 11 + """ + + def test_builtins_cast_return_none(): """Casters produced with PYBIND11_TYPE_CASTER() should convert nullptr to None""" import pybind11_tests as m