Cannot Create Constexpr Std::Vector

Cannot create constexpr std::vector

AFAIK The initlializer_list constructor of std::vector<> is not declared constexpr.

C++20 constexpr vector and string not working

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.

How to create a constexpr std::vectorstd::string or something similar?

std::vector/std::string doesn't have constexpr constructor before C++20...
and even in C++20, the constexpr allocation should not escape from constexpr evaluation, so cannot be used in runtime (for printing).

I don't see a standard constexpr way to transform an integer to a char sequence representation.
std::to_string, std::to_chars, std::format are not constexpr.

So instead of homogeneous container, you might use std::tuple, something like (C++17):

template <std::size_t I>
constexpr auto fizzbuzz_elem()
{
if constexpr (I % 5 == 0 && I % 3 == 0) {
return "FizzBuzz";
} else if constexpr (I % 5 == 0) {
return "Buzz";
} else if constexpr (I % 3 == 0){
return "Fizz";
} else {
return I;
}
}

template <std::size_t...Is>
constexpr auto fizzbuzz_impl(std::index_sequence<Is...>){
return std::make_tuple(fizzbuzz_elem<1 + Is>()...);
}

template <std::size_t N>
constexpr auto fizzbuzz(){
return fizzbuzz_impl(std::make_index_sequence<N>());
}

int main() {
constexpr auto res = fizzbuzz<42>();
std::apply([](auto... e){ ((std::cout << e << std::endl), ...); }, res);
}

Demo

Using constexpr vectors in template parameters (C++20)

You still (in C++20 and I don't think there is any change for C++23) can't use a std::vector as a non-type template argument or have any std::vector variable marked constexpr or have any constant expression resulting in a std::vector as value at all.

The only use case that is allowed now in C++20 that wasn't allowed before is to have a (non-constexpr) std::vector variable or object which is constructed while a constant expression is evaluated and destroyed before the constant evaluation ends.

This means you can now for example take the function

int f() {
std::vector<int> vec;
vec.push_back(3);
vec.push_back(1);
vec.push_back(2);
std::sort(vec.begin(), vec.end());
return vec.front();
}

add a constexpr on it and use it in constant expression e.g.

static_assert(f() == 1);

But that's all. It is still very useful, because beforehand you could only use algorithms that don't need any dynamic memory allocation to calculate something at compile-time. That meant that you often couldn't just use the usual runtime algorithm/implementation directly in a compile-time context.

The same is true for any type that keeps references to dynamically allocated memory. You need to destroy them during the constant expression evaluation, i.e. they must be temporaries or local variables in a function or return values which are not stored in a runtime context.

In the specific case of non-type template arguments the situation is even stricter. Not all types that you could make a constexpr variable of, can be used as non-type template arguments. There are much stricter restrictions. They must be so-called structural types.

These are for example fundamental types such as arithmetic types, pointers, etc. A class type is a structural type if it is a literal type and also has only non-static data members which are public and non-mutable as well as all of them, recursively, structural types as well.

I think it is clear that std::vector doesn't satisfy these requirements. std::array is explicitly specified to be a structural type, which is why you are allowed to use it as non-type template argument.

Why is std::vector::push_back declared as constexpr in C++20?

Is there any rationale behind?

This is the abstract of the proposal. There is no separate rationale section:

P1004R2 Making std::vector constexpr

Abstract

std::vector is not currently constexpr friendly. With the loosening of requirements on constexpr in [P0784R1] and related papers, we can now make std::vector constexpr, and we should in order to support the constexpr reflection effort (and other evident use cases).

Constexpr function to concatenate vectors

  1. The difference between the two functions [that makes the template version compile ...] is defined according to the standard right here [dcl.constexpr], §9.1.5.6:

If the instantiated template specialization of a constexpr function
template or member function of a class template would fail to satisfy
the requirements for a constexpr function or constexpr constructor,
that specialization is still a constexpr function or constexpr
constructor, even though a call to such a function cannot appear in a
constant expression.


  1. As you can see from the standard, the constexpr isn't ignored, but when you'll try using it as a constant it shouldn't compile.

  2. As a whole i'd say this is only the answer to your original request in case your sole reason for having vect_concat() a constexpr was delivering intent in the code (A worthy cause in its own right!). But if at any point moving onward you're to try calling it as a syntactic constant it would reveal itself as missing the entire point of constexpr itself.

Why can't I initialize this std::vector with an l-value?

TL;DR

The problem is not specific/limited to std::vector but instead is a consequence of the rule quoted below from the standard.


Let's see on case by case basis what is happening and why do we get the mentioned narrowing conversion error/warning when using lvalue.

Case 1

Here we consider:

int lvalue = 6; // lvalue is not a constant expression 

//---------------------------v------------------->constant expression so works fine
std::vector<int*> myvector { 6 };
std::vector<int*> myvector{ lvalue };
//--------------------------^^^^^^--------------->not a constant expression so doesn't work

First note that std::vector<int*> does not have an initializer list constructor that takes an initializer list of int.

So in this case the size_t count ctor will be used. Now let's see the reason for getting narrowing conversion error/warning.

The reason we get an error/warning when using the variable named lvalue while not when using a prvalue int is because in the former case lvalue is not a constant expression and so we have a narrowing conversion. This can be seen from dcl.init.list#7 which states:

A narrowing conversion is an implicit conversion

  • from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression whose value after integral promotions will fit into the target type.

(emphasis mine)

This means that the conversion from lvalue(which is an lvalue expression) which is of type int to size_t parameter of the vector's std::vector::vector(size_t, /*other parameters*/) ctor, is a narrowing conversion. But the conversion from prvalue int 6 to the size_t parameter of the vector's std::vector::vector(size_t, /*other parameters*/) is not a narrowing conversion.

To prove that this is indeed the case, lets look at some examples:

Example 1

int main()
{
//----------------v---->no warning as constant expression
std::size_t a{1};

int i = 1;
//----------------v---->warning here i is not a constant expression
std::size_t b{i};

constexpr int j = 1;
//----------------v---->no warning here as j is a constexpr expression
std::size_t c{j};
return 0;
}

Example 2

struct Custom 
{
Custom(std::size_t)
{

}
};
int main()
{
//-----------v---->constant expression
Custom c{3}; //no warning/error here as there is no narrowing conversion

int i = 3; //not a constant expressoion

//-----------v---->not a constant expression and so we get warning/error
Custom d{i}; //warning here of narrowing conversion here


constexpr int j = 3; //constant expression

//-----------v------>no warning here as j is a constant expression and so there is no narrowing conversion
Custom e{j};
return 0;
}

Demo



Case 2

Here we consider:

//------------v-------------------------->note the int here instead of int* unlike case 1 
std::vector<int> myvector{num_elements};//this uses constructor initializer list ctor

In this case there is a initializer list ctor available for std::vector<int> and it will be preferred over the size_t count constructor as we've used braces {} here instead of parenthesis (). And so a vector of size 1 will be created. More details at Why is the std::initializer_list constructor preferred when using a braced initializer list?.


On the other hand, when we use:

std::vector<int> myvector(num_elements); //this uses size_t ctor

Here the size_t ctor of std::vector will be used as the initializer list ctor is not even viable in this case as we've used parenthesis (). And so a vector of size 6 will be created. You can confirm this using the example given below:

struct Custom 
{

Custom(std::size_t)
{
std::cout<<"size t"<<std::endl;
}
Custom(std::initializer_list<int>)
{
std::cout<<"initializer_list ctor"<<std::endl;
}
};
int main()
{
Custom c(3); //uses size_t ctor, as the initializer_list ctor is not viable
return 0;
}


Related Topics



Leave a reply



Submit