Until now Cython+ merely used C++'s `std::string` to represent strings.
However I realised this would not be enough to accurately wrap the C socket APIs: they often accept `null` as a valid and meaningful value for a `char *` string argument. But there is no easy way to represent `null` with `std::string`.
So instead I wrote a minimal string implementation by wrapping a `std::string_view` (C++17) into a Cython+ GIL-free type. This is one of our next steps anyway. There are multiple advantages:
- full control over memory management: unlike `std::string`, `std::string_view` doesn't manage memory.
- in particular, it will let us take ownership of `char *` strings allocated by a C API without copying.
- aliasing a string will not create a copy, unlike with `std::string`.
- we can implement our own string methods.
- in particular, we can enforce immutability.
And since Cython+ GIL-free types are allocated to the heap and referenced by pointer, `null` is a valid value.
This is still an unfinished implementation, so I'll add to it as I need.