37

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?

6
  • Did you try to actually compile it? The message comes from Intellisense, which may not have caught up with the latest compiler features. Commented Oct 8, 2021 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). Commented Oct 8, 2021 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". Commented Oct 8, 2021 at 15:19
  • Possibly related/same bug? 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?) Commented Oct 8, 2021 at 15:21
  • @AdrianMole That is probably correct, the reference does specify that constexpr vectors and strings are not supported by clang. Commented Oct 8, 2021 at 15:23

2 Answers 2

48

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you, I've just tested with a constexpr function and it does seem that the vector is allocated at compilation time.
@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: godbolt.org/z/PGKnbehqq
8

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

3 Comments

Hi! :) Looks cool, but i don't succeed to reproduce your code: godbolt.org/z/K7KbEWW4Y
@WaterFox: You need to add using namespace std::literals; It works for me in VS2019 version 16.11.36 (specify C++20). The website you posted seems to use a wrong version of msvc (14.29).
If you are using gcc, version 13.3 is needed (Ubuntu 24.04 comes with 13.2). You can also just delete sv.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.