Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to use std::string in a constexpr?

People also ask

Can a std :: string be constexpr?

constexpr started small in C++11 but then, with each Standard revision, improved considerably. In C++20, we can say that there's a culmination point as you can even use std::vector and std::string in constant expressions!

Can std :: function be constexpr?

constexpr Containers and Algorithms of the Standard Template Library. 100 classical algorithms of the Standard Template Library are declared as constexpr . Consequently, you can sort a std::vector of ints at compile time.

Can a vector be constexpr?

Formally, that's because vector constructor is not declared constexpr .

Should constexpr be static?

So you should definitely use static constexpr in your example. However, there is one case where you wouldn't want to use static constexpr . Unless a constexpr declared object is either ODR-used or declared static , the compiler is free to not include it at all.


As of C++20, yes.

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.


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.


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: Cannot create constexpr std::vector

Tested in Ubuntu 19.04.


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;
}

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";
}