Comments on string_view: creating a constexpr array of string_views

2,277 views
Skip to first unread message

Matthew Fioravante

unread,
Dec 11, 2014, 8:17:58 PM12/11/14
to std-pr...@isocpp.org
It is a common idiom to create a global array of string literals at compile time:

foo.h
class Foo {
 
enum Color { kRed, kGreen, kBlue };
 
static constexpr const char* const kColorStr[] = { "Red", "Green", "Blue" };
};

foo.cpp
constexpr const char* const Foo::kColorStr[];

There are a few problems with this. One is that it uses a C array, which is somewhat clumsy and error prone to use because it doesn't have a size() member function (error prone sizeof() calculations to iterate using indices) and it also decays to a pointer. The strings are also represented by null terminated char* pointers which requires a strlen() call whenever you need the length. The strlen() may be optimized out in somecases, but strlen() isn't constexpr so you cannot get the string length at compile time.

What would be ideal is to have the type of kColorStr be std::array<string_view,N>. If we had a constexpr make_array() to construct a std::array from a variable number of objects along with a string view user defined literal (not in the proposal but mentioned as possibility in the future) we could do this:

static constexpr auto kColorStr = make_array("Red"sv, "Green"sv, "Blue"sv);
//decltype(kColorStr) == std::array<string_view, 3>

Using an array<string_view,N> has other advantages, namely that it automatically converts to an array_view<string_view>. This makes the string table very easy to use in your program. Here is one example.

//Assumes E starts from 0 and is contiguous
template <typename E>
std
::optional<E> enum_to_str(string_view s, carray_view<string_view> tags) {
 
for(int i = 0; i < int(tags.size()); ++i) {
   
if(s == tags[i]) {
     
return E(i);
   
}
 
}
 
return {};
}

assert(enum_to_str<Foo::Color>("Red", Foo::kColorStr) == Foo::Red);

A string_view[N] would also work here but const char* [N] or std::array<const char*, N> would not. We want things to be string_view's or strings and not char*. If the table is based on char*, you have to do a conversion to string_view and construct temporary string_view tables if you want to use string_views to interact with the table.

Using a string_view user defined literal "sv" has problems. The biggest being that we have to add a using declaration to enable it. These constexpr string tables will be defined in header files and adding using statements to a header file is never a good idea.

I solved this problem by adding a simple static method to my string_view class:

template <typename... Args>
constexpr std::array<string_view,sizeof...(Args)> string_view::make_literal_array(Args... args) {
 
return std::array<string_view,sizeof...(Args)>(string_view(args, sizeof(args)-1)...);
}

This uses the (char*, size_t) constructor because there is no array constructor (for good reason, see the paper), and the char* constructor calls strlen() which is not constexpr. It also correctly supports string literals with embedded nulls. This function will produce garbage if you don't feed it string literals but we can probably add some static_asserts to catch some of the incorrect usage. The name is obvious enough to not use it the wrong way.

Example usage:

foo.h
class Foo {
 
enum Color { kRed, kGreen, kBlue };
 
static constexpr auto kColorStr = string_view::make_literal_array("Red", "Green", "Blue");
};

foo.cpp
constexpr decltype(Foo::kColorStr) Foo::kColorStr;

Maybe something like my make_literal_array should be standardized? How would you create a global constexpr array<string_view,N>?



jeanmichae...@gmail.com

unread,
Sep 24, 2016, 4:59:10 AM9/24/16
to ISO C++ Standard - Future Proposals
I found this looking for the same problem.

There is just a minor bug with your implementation of make_literal_array :
Args should be passed as forwarding reference (is this the official term?) else the char[] arrays decays into char*
during expansion of the parameter pack, and sizeof(args) will become sizeof(char*) which
may more often than not produce unexpected results.

Best,
Jean-Michaël

Matthew Fioravante

unread,
Sep 24, 2016, 7:48:10 AM9/24/16
to ISO C++ Standard - Future Proposals, jeanmichae...@gmail.com


On Saturday, September 24, 2016 at 9:59:10 AM UTC+1, jeanmichae...@gmail.com wrote:
I found this looking for the same problem.

There is just a minor bug with your implementation of make_literal_array :
Args should be passed as forwarding reference (is this the official term?) else the char[] arrays decays into char*
during expansion of the parameter pack, and sizeof(args) will become sizeof(char*) which
may more often than not produce unexpected results.


Yes you're right, actually passing by const reference is sufficient since string_views and string literals are always const.

Nowadays I do it with variadic size_t....

template <size_t N>
constexpr string_view sv(const char (&literal)[N]) {
	return string_view(literal, N - 1);
}

template <size_t... N> constexpr std::array<string_view, sizeof...(N)> sva( const char (&... literals)[N]) { return {{sv(literals)...}}; }

constexpr auto colors = sva("Red", "Green", "Blue");

I usually add these helpers to every project I start. It would be useful to have some variant of this in the standard.


Reply all
Reply to author
Forward
0 new messages