The motivations behind std::string_view and std::span are similar. The string_view and span are objects that refer to a contiguous sequence of elements starting at position zero and provide standard container operations. Both types are lightweight easy-to-copy objects comprising a pointer and a size member. Conceptually, they are non-owning views of an array (or contiguous sequence) that provide the rich standard interface.
As for the differences between the two, the std::span<T
> is a general-purpose array view template, whereas the std::string_view is a more specialized view on a char
sequence or string.
These are the brief descriptions of notable differences between the two:
The span is a template that works with any user-defined or primitive type, whereas the string_view is specifically a view on a contiguous char
array. Superficially, a string_view is equivalent to span<char
>:
char buff[] = "Hello World";
auto sp = std::span<char>(&buff[0], 5);
auto sv = std::string_view(&buff[6], 5);
for(auto c : sp)
std::cout << c; //Hello
std::cout << " ";
for(auto c : sv)
std::cout << c; //World
//Above code prints: Hello World
But string_view is more than a span<char
>, read on the next two differences to understand more.
One distinctive feature of string_view is that it is a read-only view. Therefore, we cannot change an underlying array through a string_view, though we can change an underlying array through a span:
char str[] = "hello";
//Change str to uppercase through span
auto sp = std::span<char>(str,strlen(str));
std::transform(sp.begin(), sp.end(), sp.begin(),
[](char c) { return std::toupper(c); });
//str is now HELLO
//Back to lowercase via string_view is not possible.
auto sv = std::string_view(str);
std::transform(sv.begin(), sv.end(), sv.begin(),
[](char c) { return std::tolower(c); }); //ERROR!
If string_view is overtly read-only, the span is flexible enough to be turned into a read-only view through span<const T
>. Therefore, a string_view is closer to a span<const char
>. But wait, before you question the rationality behind string_view, there is one more important difference that warrants the existence of the string_view.
The span has several sequential container operations, e.g., front, back, begin, end, operator[]. However, string_view has several more std::string-like methods, e.g., substr, find, compare, overloaded comparison operators (e.g., ==,<,>). Therefore, the string_view can eliminate the need to create an std::string object in some cases where the underlying storage does not matter.
Considering the similarities and differences between the two, we can say that the std::string_view is to std::string what std::span<const char
> is to std::vector<char
>.
Let's look at an example that throws more light on this subject. Suppose we have a char
array that stores many fixed-length 3-letter alphabetical codes. This char
array, buffer, stays in the memory for the lifetime of the process:
char buffer[] = "CAB"
"DEF"
"GAG"
"DAD"
"RAP"
/*more*/; //A buffer with many codes
We split the above array into many objects of a type Code. The Code can be a lightweight array view object. However, we want to use the Code as a key in an std::map to store some data against it, as follows:
struct SomeData {
//...
};
std::map<Code, SomeData> codesData;
We are thinking of to declare Code an alias of either string_view or span<const char
>:
using Code = std::string_view;
// OR
using Code = std::span<const char>;
/*Data can be loaded into
the map for each Code as: */
for(size_t i=0; i < sizeof buffer; i=i+3)
codesData.emplace(Code(&buffer[i], 3), SomeData());
Which one (std::string_view or std::span<const char
>) do you think can satisfy our requirement as Code? Select below (check Explanations
for details):