C++ allows constexpr functions that return std::string. For example, using magic_enum to get a space-separated string containing all the enum values.
template <E e>
static constexpr std::string GetAllEnumNamesAsString()
{
const std::string space = " ";
constexpr std::array names = magic_enum::enum_names<E>();
std::string all{ names[0] };
for (int i = 1; i < names.size(); i++)
all += (space + std::string{ names[i] });
return all;
}
The problem is, while a string can be used in a constexpr function, you cannot declare a constexpr string because it uses the heap. So code using this returned string can only declare it as const. I thought that the compiler would at least use the constexpr function to calculate the final string buffer and put that in static storage, but both GCC and MSVC just delegate the entire call to runtime, causing a ton of extraneous code to be generated. Godbolt link:
int main()
{
// must be allowed at compile time because it's part of a static_assert
static_assert(GetAllNames<MyEnum>().length() == 17);
// but compiler makes this pure runtime - not even the final string is placed in static storage to copy to the heap
const std::string names = GetAllNames<MyEnum>();
printf("%s\n", names.c_str());
return 0;
}
What's the best way to get around this to actually use the compiler-calculated string buffer? In my limited understanding, constexpr allows std::string as 'transient' so we need to copy it out of the string to somewhere before the function that uses the string is destroyed (to an std::array?). The trick here is that you cannot use a variable as a template parameter. To get around this, we just need to... call the function twice? The following works, but I must be missing something - is there something standard out there already for this? Or at least a better way to not 'throw away' all the work the compiler can do in constexpr functions returning a string?
template <size_t Length>
class FakeStringView
{
public:
constexpr FakeStringView(const std::string_view& s1) : _array{}
{
for (auto i = 0; i < s1.length(); ++i)
_array[i] = s1[i];
}
constexpr std::string_view Get() const
{
return { _array.data(), _array.size() };
}
private:
std::array<char, Length> _array;
};
template <typename E>
constexpr auto GetAllNames()
{
// call it twice - once for length and once for the string
return FakeStringView<GetAllNamesImpl<E>().length()>(GetAllNamesImpl<E>());
}
constexpr auto fakestringview = GetAllNames<MyEnum>();
std::cout << fakestringivew.Get();
std::string/std::vectorcould be used in constexpr function, we had to create a function to get the size, and then a function to construct the array.#includeurl file instead of copy pasted file content (header requires to be self content though). links updated.