I'm trying to use modern string-handling approaches (like std::string_view or GSL's string_span) to interact with a C API (DBus) that takes strings as null-terminated const char*s, e.g.
DBusMessage* dbus_message_new_method_call(
const char* destination,
const char* path,
const char* iface,
const char* method
)
string_view and string_span don't guarantee that their contents are null-terminated - since spans are (char* start, ptrdiff_t length) pairs, that's largely the point. But GSL also provides a zstring_view, which is guaranteed to be null-terminated. The comments around zstring_span suggest that it's designed exactly for working with legacy and C APIs, but I ran into several sticking points as soon as I started using it:
Representing a string literal as a
string_spanis trivial:cstring_span<> bar = "easy peasy";but representing one as a
zstring_spanrequires you to wrap the literal in a helper function:czstring_span<> foo = ensure_z("odd");This makes declarations noisier, and it also seems odd that a literal (which is guaranteed to be null-terminated) isn't implicitly convertible to a
zstring_span.ensure_z()also isn'tconstexpr, unlike constructors and conversions forstring_span.There's a similar oddity with
std::string, which is implicitly convertible tostring_span, but notzstring_span, even thoughstd::string::data()has been guaranteed to return a null-terminated sequence since C++11. Again, you have to callensure_z():zstring_span<> to_zspan(std::string& s) { return ensure_z(s); }There seems to be some const-correctness issues. The above works, but
czstring_span<> to_czspan(const std::string& s) { return ensure_z(s); }fails to compile, with errors about being unable to convert from
span<char, ...>tospan<const char, ...>This is a smaller point than the others, but the member function that returns a
char*(which you would feed to a C API like DBus) is calledassume_z(). What's being assumed when the constructor ofzstring_spanexpects a null-terminated range?
If zstring_span is designed "to convert zero-terminated spans to legacy strings", why does its use here seem so cumbersome? Am I misusing it? Is there something I'm overlooking?
It's "cumbersome" in part because it's intended to be.
This:
Is not a safe operation. Why? Because while it is true that
sis NUL terminated, it is entirely possible that the actualscontains internal NUL characters. That's a legitimate thing you can do withstd::string, butzstring_spanand whomever takes it can't handle that. They'll truncate the string.By contrast,
string_span/viewconversions are safe from this perspective. Consumers of such strings take a sized string and thus can handle embedded NULs.Because the
zstring_spanconversion is unsafe, there should be some explicit notation that something potentially unsafe is being done.ensure_zrepresents that explicit notation.Another problem is that C++ has no mechanism to tell the difference between a literal string argument and any old
const char*orconst char[]parameter. Since a bareconst char*may or may not be a string literal, you have to assume that it isn't and therefore use a more verbose conversion.Also, C++ string literals can contain embedded NUL characters, so the above reasoning applies.
The
constissue seems like a code bug, and you should probably file it as such.