Skip to content

Commit f42af24

Browse files
committed
Support std::string_view when compiled under C++17
1 parent 220a77f commit f42af24

File tree

5 files changed

+106
-7
lines changed

5 files changed

+106
-7
lines changed

β€Ždocs/advanced/cast/overview.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ as arguments and return values, refer to the section on binding :ref:`classes`.
116116
+------------------------------------+---------------------------+-------------------------------+
117117
| ``std::wstring`` | STL dynamic wide string | :file:`pybind11/pybind11.h` |
118118
+------------------------------------+---------------------------+-------------------------------+
119+
| ``std::string_view``, | STL C++17 string views | :file:`pybind11/pybind11.h` |
120+
| ``std::u16string_view``, etc. | | |
121+
+------------------------------------+---------------------------+-------------------------------+
119122
| ``std::pair<T1, T2>`` | Pair of two custom types | :file:`pybind11/pybind11.h` |
120123
+------------------------------------+---------------------------+-------------------------------+
121124
| ``std::tuple<...>`` | Arbitrary tuple of types | :file:`pybind11/pybind11.h` |

β€Ždocs/advanced/cast/strings.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,15 @@ expressed as a single Unicode code point
287287
no way to capture them in a C++ character type.
288288

289289

290+
C++17 string views
291+
==================
292+
293+
C++17 string views are automatically supported when compiling in C++17 mode.
294+
They follow the same rules for encoding and decoding as the corresponding STL
295+
string type (for example, a ``std::u16string_view`` argument will be passed
296+
UTF-16-encoded data, and a returned ``std::string_view`` will be decoded as
297+
UTF-8).
298+
290299
References
291300
==========
292301

β€Žinclude/pybind11/cast.h

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@
1818
#include <tuple>
1919
#include <cstring>
2020

21+
#if defined(PYBIND11_CPP17)
22+
# if defined(__has_include)
23+
# if __has_include(<string_view>)
24+
# define PYBIND11_HAS_STRING_VIEW
25+
# endif
26+
# elif defined(_MSC_VER)
27+
# define PYBIND11_HAS_STRING_VIEW
28+
# endif
29+
#endif
30+
#ifdef PYBIND11_HAS_STRING_VIEW
31+
#include <string_view>
32+
#endif
33+
2134
NAMESPACE_BEGIN(pybind11)
2235
NAMESPACE_BEGIN(detail)
2336
// Forward declarations:
@@ -1003,10 +1016,11 @@ template <> class type_caster<bool> {
10031016
};
10041017

10051018
// Helper class for UTF-{8,16,32} C++ stl strings:
1006-
template <typename CharT, class Traits, class Allocator>
1007-
struct type_caster<std::basic_string<CharT, Traits, Allocator>, enable_if_t<is_std_char_type<CharT>::value>> {
1019+
template <typename StringType, bool IsView = false> struct string_caster {
1020+
using CharT = typename StringType::value_type;
1021+
10081022
// Simplify life by being able to assume standard char sizes (the standard only guarantees
1009-
// minimums), but Python requires exact sizes
1023+
// minimums, but Python requires exact sizes)
10101024
static_assert(!std::is_same<CharT, char>::value || sizeof(CharT) == 1, "Unsupported char size != 1");
10111025
static_assert(!std::is_same<CharT, char16_t>::value || sizeof(CharT) == 2, "Unsupported char16_t size != 2");
10121026
static_assert(!std::is_same<CharT, char32_t>::value || sizeof(CharT) == 4, "Unsupported char32_t size != 4");
@@ -1015,8 +1029,6 @@ struct type_caster<std::basic_string<CharT, Traits, Allocator>, enable_if_t<is_s
10151029
"Unsupported wchar_t size != 2/4");
10161030
static constexpr size_t UTF_N = 8 * sizeof(CharT);
10171031

1018-
using StringType = std::basic_string<CharT, Traits, Allocator>;
1019-
10201032
bool load(handle src, bool) {
10211033
#if PY_MAJOR_VERSION < 3
10221034
object temp;
@@ -1050,11 +1062,16 @@ struct type_caster<std::basic_string<CharT, Traits, Allocator>, enable_if_t<is_s
10501062
size_t length = (size_t) PYBIND11_BYTES_SIZE(utfNbytes.ptr()) / sizeof(CharT);
10511063
if (UTF_N > 8) { buffer++; length--; } // Skip BOM for UTF-16/32
10521064
value = StringType(buffer, length);
1065+
1066+
// If we're loading a string_view we need to keep the encoded Python object alive:
1067+
if (IsView)
1068+
view_into = std::move(utfNbytes);
1069+
10531070
return true;
10541071
}
10551072

10561073
static handle cast(const StringType &src, return_value_policy /* policy */, handle /* parent */) {
1057-
const char *buffer = reinterpret_cast<const char *>(src.c_str());
1074+
const char *buffer = reinterpret_cast<const char *>(src.data());
10581075
ssize_t nbytes = ssize_t(src.size() * sizeof(CharT));
10591076
handle s = decode_utfN(buffer, nbytes);
10601077
if (!s) throw error_already_set();
@@ -1064,6 +1081,8 @@ struct type_caster<std::basic_string<CharT, Traits, Allocator>, enable_if_t<is_s
10641081
PYBIND11_TYPE_CASTER(StringType, _(PYBIND11_STRING_NAME));
10651082

10661083
private:
1084+
object view_into;
1085+
10671086
static handle decode_utfN(const char *buffer, ssize_t nbytes) {
10681087
#if !defined(PYPY_VERSION)
10691088
return
@@ -1101,6 +1120,16 @@ struct type_caster<std::basic_string<CharT, Traits, Allocator>, enable_if_t<is_s
11011120
bool load_bytes(enable_if_t<sizeof(C) != 1, handle>) { return false; }
11021121
};
11031122

1123+
template <typename CharT, class Traits, class Allocator>
1124+
struct type_caster<std::basic_string<CharT, Traits, Allocator>, enable_if_t<is_std_char_type<CharT>::value>>
1125+
: string_caster<std::basic_string<CharT, Traits, Allocator>> {};
1126+
1127+
#ifdef PYBIND11_HAS_STRING_VIEW
1128+
template <typename CharT, class Traits>
1129+
struct type_caster<std::basic_string_view<CharT, Traits>, enable_if_t<is_std_char_type<CharT>::value>>
1130+
: string_caster<std::basic_string_view<CharT, Traits>, true> {};
1131+
#endif
1132+
11041133
// Type caster for C-style strings. We basically use a std::string type caster, but also add the
11051134
// ability to use None as a nullptr char* (which the string caster doesn't allow).
11061135
template <typename CharT> struct type_caster<CharT, enable_if_t<is_std_char_type<CharT>::value>> {

β€Žtests/test_python_types.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,21 @@ test_initializer python_types([](py::module &m) {
555555
m.def("nodefer_none_optional", [](py::none) { return false; });
556556
#endif
557557

558+
#ifdef PYBIND11_HAS_STRING_VIEW
559+
m.attr("has_string_view") = true;
560+
m.def("string_view_print", [](std::string_view s) { py::print(s, s.size()); });
561+
m.def("string_view16_print", [](std::u16string_view s) { py::print(s, s.size()); });
562+
m.def("string_view32_print", [](std::u32string_view s) { py::print(s, s.size()); });
563+
m.def("string_view_chars", [](std::string_view s) { py::list l; for (auto c : s) l.append((std::uint8_t) c); return l; });
564+
m.def("string_view16_chars", [](std::u16string_view s) { py::list l; for (auto c : s) l.append((int) c); return l; });
565+
m.def("string_view32_chars", [](std::u32string_view s) { py::list l; for (auto c : s) l.append((int) c); return l; });
566+
m.def("string_view_return", []() { return std::string_view(u8"utf8 secret \U0001f382"); });
567+
m.def("string_view16_return", []() { return std::u16string_view(u"utf16 secret \U0001f382"); });
568+
m.def("string_view32_return", []() { return std::u32string_view(U"utf32 secret \U0001f382"); });
569+
#else
570+
m.attr("has_string_view") = false;
571+
#endif
572+
558573
m.def("return_capsule_with_destructor",
559574
[]() {
560575
py::print("creating capsule");

β€Žtests/test_python_types.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import pytest
33
import pybind11_tests
44

5-
from pybind11_tests import ExamplePythonTypes, ConstructorStats, has_optional, has_exp_optional
5+
from pybind11_tests import (ExamplePythonTypes, ConstructorStats, has_optional, has_exp_optional,
6+
has_string_view)
67

78

89
def test_repr():
@@ -558,6 +559,48 @@ def test_bytes_to_string():
558559
assert string_length(u'πŸ’©'.encode("utf8")) == 4
559560

560561

562+
@pytest.mark.skipif(not has_string_view, reason='no <string_view>')
563+
def test_string_view(capture):
564+
"""Tests support for C++17 string_view arguments and return values"""
565+
from pybind11_tests import (string_view_print, string_view16_print, string_view32_print,
566+
string_view_chars, string_view16_chars, string_view32_chars,
567+
string_view_return, string_view16_return, string_view32_return)
568+
569+
assert string_view_chars("Hi") == [72, 105]
570+
assert string_view_chars("Hi πŸŽ‚") == [72, 105, 32, 0xf0, 0x9f, 0x8e, 0x82]
571+
assert string_view16_chars("Hi πŸŽ‚") == [72, 105, 32, 0xd83c, 0xdf82]
572+
assert string_view32_chars("Hi πŸŽ‚") == [72, 105, 32, 127874]
573+
574+
assert string_view_return() == "utf8 secret πŸŽ‚"
575+
assert string_view16_return() == "utf16 secret πŸŽ‚"
576+
assert string_view32_return() == "utf32 secret πŸŽ‚"
577+
578+
with capture:
579+
string_view_print("Hi")
580+
string_view_print("utf8 πŸŽ‚")
581+
string_view16_print("utf16 πŸŽ‚")
582+
string_view32_print("utf32 πŸŽ‚")
583+
584+
assert capture == """
585+
Hi 2
586+
utf8 πŸŽ‚ 9
587+
utf16 πŸŽ‚ 8
588+
utf32 πŸŽ‚ 7
589+
"""
590+
591+
with capture:
592+
string_view_print("Hi, ascii")
593+
string_view_print("Hi, utf8 πŸŽ‚")
594+
string_view16_print("Hi, utf16 πŸŽ‚")
595+
string_view32_print("Hi, utf32 πŸŽ‚")
596+
assert capture == """
597+
Hi, ascii 9
598+
Hi, utf8 πŸŽ‚ 13
599+
Hi, utf16 πŸŽ‚ 12
600+
Hi, utf32 πŸŽ‚ 11
601+
"""
602+
603+
561604
def test_builtins_cast_return_none():
562605
"""Casters produced with PYBIND11_TYPE_CASTER() should convert nullptr to None"""
563606
import pybind11_tests as m

0 commit comments

Comments
Β (0)