Is it possible to use std::string in a constexpr?
C++C++11ConstexprStdstringC++ Problem Overview
Using C++11, Ubuntu 14.04, GCC default toolchain.
This code fails:
constexpr std::string constString = "constString";
> error: the type ‘const string {aka const std::basic_string
Is it possible to use std::string
in aconstexpr
? (apparently not...) If so, how? Is there an alternative way to use a character string in a constexpr
?
C++ Solutions
Solution 1 - C++
As of C++20, yes, but only if the std::string
is destroyed by the end of constant evaluation. So while your example will still not compile, something like this will:
constexpr std::size_t n = std::string("hello, world").size();
However, as of C++17, you can use string_view
:
constexpr std::string_view sv = "hello, world";
A string_view
is a string
-like object that acts as an immutable, non-owning reference to any sequence of char
objects.
Solution 2 - C++
No, and your compiler already gave you a comprehensive explanation.
But you could do this:
constexpr char constString[] = "constString";
At runtime, this can be used to construct a std::string
when needed.
Solution 3 - C++
C++20 will add constexpr
strings and vectors
The following proposal has been accepted apparently: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0980r0.pdf and it adds constructors such as:
// 20.3.2.2, construct/copy/destroy
constexpr
basic_string() noexcept(noexcept(Allocator())) : basic_string(Allocator()) { }
constexpr
explicit basic_string(const Allocator& a) noexcept;
constexpr
basic_string(const basic_string& str);
constexpr
basic_string(basic_string&& str) noexcept;
in addition to constexpr versions of all / most methods.
There is no support as of GCC 9.1.0, the following fails to compile:
#include <string>
int main() {
constexpr std::string s("abc");
}
with:
g++-9 -std=c++2a main.cpp
with error:
error: the type ‘const string’ {aka ‘const std::__cxx11::basic_string<char>’} of ‘constexpr’ variable ‘s’ is not literal
std::vector
discussed at: https://stackoverflow.com/questions/33241909/cannot-create-constexpr-stdvector
Tested in Ubuntu 19.04.
Solution 4 - C++
Since the problem is the non-trivial destructor so if the destructor is removed from the std::string
, it's possible to define a constexpr
instance of that type. Like this
struct constexpr_str {
char const* str;
std::size_t size;
// can only construct from a char[] literal
template <std::size_t N>
constexpr constexpr_str(char const (&s)[N])
: str(s)
, size(N - 1) // not count the trailing nul
{}
};
int main()
{
constexpr constexpr_str s("constString");
// its .size is a constexpr
std::array<int, s.size> a;
return 0;
}
Solution 5 - C++
C++20 is a step toward making it possible to use std::string
at compile time, but P0980 will not allow you to write code like in your question:
constexpr std::string constString = "constString";
the reason is that constexpr
std::string
is allowed only to be used in constexpr
function (constant expression evaluation context). Memory allocated by constexpr
std::string
must be freed before such function returns - this is the so called transient allocation, and this memory cannot 'leak' outside to runtime to constexpr
objects (stored in data segments) accessible at runtime . For example compilation of above line of code in current VS2022 preview (cl version : 19.30.30704) results in following error:
1> : error C2131: expression did not evaluate to a constant
1> : message : (sub-)object points to memory which was heap allocated during constant evaluation
this is because it tries to make a non-transient allocation which is not allowed - this would mean allocation into a data segment of the compiled binary.
In p0784r1, in "Non-transient allocation" paragraph, you can find that there is a plan to allow conversion of transient into static memory (emphasis mine):
> What about storage that hasn't been deallocated by the time evaluation > completes? We could just disallow that, but there are really > compelling use cases where this might be desirable. E.g., this could > be the basis for a more flexible kind of "string literal" class. We > therefore propose that if a non-transient constexpr allocation is > valid (to be described next), the allocated objects are promoted to > static storage duration.
There is a way to export transient std::string
data outside to make it usable at runtime. You must copy it to std::array
, the problem is to compute the final size of std::array
, you can either preset some large size or compute std::string
twice - once to get size and then to get atual data. Following code successfully compiles and runs on current VS2022 preview 5. It basicly joins three words with a delimiter between words:
constexpr auto join_length(const std::vector<std::string>& vec, char delimiter) {
std::size_t length = std::accumulate(vec.begin(), vec.end(), 0,
[](std::size_t sum, const std::string& s) {
return sum + s.size();
});
return length + vec.size();
}
template<size_t N>
constexpr std::array<char, N+1> join_to_array(const std::vector<std::string>& vec, char delimiter) {
std::string result = std::accumulate(std::next(vec.begin()), vec.end(),
vec[0],
[&delimiter](const std::string& a, const std::string& b) {
return a + delimiter + b;
});
std::array<char, N+1> arr = {};
int i = 0;
for (auto c : result) {
arr[i++] = c;
}
return arr;
}
constexpr std::vector<std::string> getWords() {
return { "one", "two", "three" };
}
int main()
{
constexpr auto arr2 = join_to_array<join_length(getWords(), ';')>(getWords(), ';');
static_assert(std::string(&arr2[0]) == "one;two;three");
std::cout << &arr2[0] << "\n";
}