31

I'm getting a strange compiler error when trying to create constexpr std::string and std::vector objects:

#include <vector>
#include <string>

int main()
{
    constexpr std::string cs{ "hello" };
    constexpr std::vector cv{ 1, 2, 3 };
    return 0;
}

The compiler complains that "the expression must have a constant value":

compiler error

Am I missing something? I am using the latest Microsoft Visual Studio 2019 version: 16.11.4, and the reference (https://en.cppreference.com/w/cpp/compiler_support) states that constexpr strings and vectors are supported by this compiler version:

enter image description here

I have also tried the constexpr std::array, which does work. Could the issue have anything to do with the dynamic memory allocation associated with vectors?

Vlad
  • 519
  • 4
  • 13
  • Did you try to actually compile it? The message comes from Intellisense, which may not have caught up with the latest compiler features. – Eugene Oct 08 '21 at 15:16
  • @康桓瑋 Are you talking about the C++ Language Standard option? If yes, it is set to ISO C++20 Standard (/std:c++20). – Vlad Oct 08 '21 at 15:16
  • @Eugene Yes, I did try, and it throws 2 errors for each object instantiation: "error C2131: expression did not evaluate to a constant", followed by "xmemory(1341,38): message : failure was caused by allocated storage not being deallocated". – Vlad Oct 08 '21 at 15:19
  • [Possibly related/same bug](https://stackoverflow.com/q/63558400/10871073)? However, I tried your code in VS-2019 using the clang-cl compiler and get similar errors. (Maybe clang-cl doesn't yet support C++20?) – Adrian Mole Oct 08 '21 at 15:21
  • @AdrianMole That is probably correct, the reference does specify that constexpr vectors and strings are not supported by clang. – Vlad Oct 08 '21 at 15:23

2 Answers2

43

Your program is actually ill-formed, though the error may be hard to understand. constexpr allocation support in C++20 is limited - you can only have transient allocation. That is, the allocation has to be completely deallocated by the end of constant evaluation.

So you cannot write this:

int main() {
    constexpr std::vector<int> v = {1, 2, 3};
}

Because v's allocation persists - it is non-transient. That's what the error is telling you:

<source>(6): error C2131: expression did not evaluate to a constant
<source>(6): note: (sub-)object points to memory which was heap allocated during constant evaluation

v can't be constant because it's still holding on to heap allocation, and it's not allowed to do so.

But you can write this:

constexpr int f() {
    std::vector<int> v = {1, 2, 3};
    return v.size();
}

static_assert(f() == 3);

Here, v's allocation is transient - the memory is deallocated when f() returns. But we can still use a std::vector during constexpr time.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thank you, I've just tested with a `constexpr` function and it does seem that the vector is allocated at compilation time. – Vlad Oct 08 '21 at 16:01
  • The example with constexpr f() doesn't compile either: variable ‘v’ of non-literal type ‘std::vector’ in ‘constexpr’ function. – mentalmushroom Sep 06 '22 at 07:02
  • 1
    @mentalmushroom It does (there's even a link). Your compiler either isn't new enough to support constexpr allocation or your standard library doesn't support constexpr vector yet. The latest gcc, clang, and msvc all support it: https://godbolt.org/z/PGKnbehqq – Barry Sep 06 '22 at 15:05
4

As @barry explained, you cannot create variables which requires dynamic allocation and which will be still available at runtime. I believe that this is explained by the following exclusion in :

An expression E is a core constant expression unless the evaluation of E, following the rules of the abstract machine ([intro.execution]), would evaluate one of the following:

https://eel.is/c++draft/expr.const#5.17

a new-expression ([expr.new]), unless the selected allocation function is a replaceable global allocation function ([new.delete.single], [new.delete.array]) and the allocated storage is deallocated within the evaluation of E;

Still you can do amazing things with this new features. For example join strings:

constexpr std::string join(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;
    });
  return result;
}

static_assert(join({ "one", "two", "three" }, ';') == "one;two;three"sv);
marcinj
  • 48,511
  • 9
  • 79
  • 100