Skip to content

Commit d7c5d27

Browse files
committed
Expand string_view support to str, bytes, memoryview
1. Allows constructing a str or bytes implicitly from a string_view; this is essentially a small shortcut allowing a caller to write `py::bytes{sv}` rather than `py::bytes{sv.data(), sv.size()}`. 2. Allows implicit conversion *to* string_view from py::bytes -- this saves a fair bit more as currently there is no simple way to get such a view of the bytes without copying it (or resorting to Python API calls). (This is not done for `str` because when the str contains unicode we have to allocate to a temporary and so there might not be some string data we can properly view without owning.) 3. Allows `memoryview::from_memory` to accept a string_view. As with the other from_memory calls, it's entirely your responsibility to keep it alive. This also required moving the string_view availability detection into detail/common.h because this PR needs it in pytypes.h, which is higher up the include chain than cast.h where it was being detected currently.
1 parent 70a58c5 commit d7c5d27

File tree

5 files changed

+71
-17
lines changed

5 files changed

+71
-17
lines changed

include/pybind11/cast.h

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,6 @@
2727
#include <utility>
2828
#include <vector>
2929

30-
#if defined(PYBIND11_CPP17)
31-
# if defined(__has_include)
32-
# if __has_include(<string_view>)
33-
# define PYBIND11_HAS_STRING_VIEW
34-
# endif
35-
# elif defined(_MSC_VER)
36-
# define PYBIND11_HAS_STRING_VIEW
37-
# endif
38-
#endif
39-
#ifdef PYBIND11_HAS_STRING_VIEW
40-
#include <string_view>
41-
#endif
42-
43-
#if defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201811L
44-
# define PYBIND11_HAS_U8STRING
45-
#endif
46-
4730
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
4831
PYBIND11_NAMESPACE_BEGIN(detail)
4932

include/pybind11/detail/common.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,24 @@
183183
# define PYBIND11_HAS_VARIANT 1
184184
#endif
185185

186+
#if defined(PYBIND11_CPP17)
187+
# if defined(__has_include)
188+
# if __has_include(<string_view>)
189+
# define PYBIND11_HAS_STRING_VIEW
190+
# endif
191+
# elif defined(_MSC_VER)
192+
# define PYBIND11_HAS_STRING_VIEW
193+
# endif
194+
#endif
195+
#ifdef PYBIND11_HAS_STRING_VIEW
196+
#include <string_view>
197+
#endif
198+
199+
#if defined(__cpp_lib_char8_t) && __cpp_lib_char8_t >= 201811L
200+
# define PYBIND11_HAS_U8STRING
201+
#endif
202+
203+
186204
#include <Python.h>
187205
#include <frameobject.h>
188206
#include <pythread.h>

include/pybind11/pytypes.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,17 @@ class str : public object {
10851085
// NOLINTNEXTLINE(google-explicit-constructor)
10861086
str(const std::string &s) : str(s.data(), s.size()) { }
10871087

1088+
#ifdef PYBIND11_HAS_STRING_VIEW
1089+
// NOLINTNEXTLINE(google-explicit-constructor)
1090+
str(std::string_view s) : str(s.data(), s.size()) { }
1091+
1092+
# ifdef PYBIND11_HAS_U8STRING
1093+
// NOLINTNEXTLINE(google-explicit-constructor)
1094+
str(std::u8string_view s) : str(s.data(), s.zie()) { }
1095+
# endif
1096+
1097+
#endif
1098+
10881099
explicit str(const bytes &b);
10891100

10901101
/** \rst
@@ -1167,6 +1178,20 @@ class bytes : public object {
11671178
pybind11_fail("Unable to extract bytes contents!");
11681179
return std::string(buffer, (size_t) length);
11691180
}
1181+
1182+
#ifdef PYBIND11_HAS_STRING_VIEW
1183+
// NOLINTNEXTLINE(google-explicit-constructor)
1184+
bytes(std::string_view s) : bytes(s.data(), s.size()) { }
1185+
1186+
operator std::string_view() const {
1187+
char *buffer = nullptr;
1188+
ssize_t length = 0;
1189+
if (PYBIND11_BYTES_AS_STRING_AND_SIZE(m_ptr, &buffer, &length))
1190+
pybind11_fail("Unable to extract bytes contents!");
1191+
return {buffer, static_cast<size_t>(length)};
1192+
}
1193+
#endif
1194+
11701195
};
11711196
// Note: breathe >= 4.17.0 will fail to build docs if the below two constructors
11721197
// are included in the doxygen group; close here and reopen after as a workaround
@@ -1714,6 +1739,13 @@ class memoryview : public object {
17141739
static memoryview from_memory(const void *mem, ssize_t size) {
17151740
return memoryview::from_memory(const_cast<void*>(mem), size, true);
17161741
}
1742+
1743+
#ifdef PYBIND11_HAS_STRING_VIEW
1744+
static memoryview from_memory(std::string_view mem) {
1745+
return from_memory(const_cast<char*>(mem.data()), static_cast<ssize_t>(mem.size()), true);
1746+
}
1747+
#endif
1748+
17171749
#endif
17181750
};
17191751

tests/test_builtin_casters.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,23 @@ TEST_SUBMODULE(builtin_casters, m) {
140140
m.def("string_view16_return", []() { return std::u16string_view(u"utf16 secret \U0001f382"); });
141141
m.def("string_view32_return", []() { return std::u32string_view(U"utf32 secret \U0001f382"); });
142142

143+
// The inner lambdas here are to also test implicit conversion
144+
using namespace std::literals;
145+
m.def("string_view_bytes", []() { return [](py::bytes b) { return b; }("abc \x80\x80 def"sv); });
146+
m.def("string_view_str", []() { return [](py::str s) { return s; }("abc \342\200\275 def"sv); });
147+
m.def("string_view_from_bytes", [](py::bytes b) { return [](std::string_view s) { return s; }(b); });
148+
#if PY_MAJOR_VERSION >= 3
149+
m.def("string_view_memoryview", []() {
150+
static constexpr auto val = "Have some \360\237\216\202"sv;
151+
return py::memoryview::from_memory(val);
152+
});
153+
#endif
154+
143155
# ifdef PYBIND11_HAS_U8STRING
144156
m.def("string_view8_print", [](std::u8string_view s) { py::print(s, s.size()); });
145157
m.def("string_view8_chars", [](std::u8string_view s) { py::list l; for (auto c : s) l.append((std::uint8_t) c); return l; });
146158
m.def("string_view8_return", []() { return std::u8string_view(u8"utf8 secret \U0001f382"); });
159+
m.def("string_view8_str", []() { return py::str{std::u8string_view{u8"abc ‽ def"}}; });
147160
# endif
148161
#endif
149162

tests/test_builtin_casters.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,14 @@ def test_string_view(capture):
206206
"""
207207
)
208208

209+
assert m.string_view_bytes() == b'abc \x80\x80 def'
210+
assert m.string_view_str() == 'abc ‽ def'
211+
assert m.string_view_from_bytes('abc ‽ def'.encode()) == 'abc ‽ def'
212+
if hasattr(m, "has_u8string"):
213+
assert m.string_view8_str() == 'abc ‽ def'
214+
if not env.PY2:
215+
assert m.string_view_memoryview() == 'Have some 🎂'.encode()
216+
209217

210218
def test_integer_casting():
211219
"""Issue #929 - out-of-range integer values shouldn't be accepted"""

0 commit comments

Comments
 (0)