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
- 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 aconstexpr
function orconstexpr
constructor,
that specialization is still aconstexpr
function orconstexpr
constructor, even though a call to such a function cannot appear in a
constant expression.
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.As a whole i'd say this is only the answer to your original request in case your sole reason for having
vect_concat()
aconstexpr
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 ofconstexpr
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
C++ Trying to Swap Values in a Vector
Why Was Pair Range Access Removed from C++11
Vc++ Fatal Error Lnk1168: Cannot Open Filename.Exe for Writing
Broken C++ Std Libraries on MACos High Sierra 10.13
How to Create a C++ Boost Undirected Graph and Traverse It in Depth First Search (Dfs) Order
How to Use Libraries Compiled with Mingw in Msvc
C++ View Types: Pass by Const& or by Value
Qt - Remove All Widgets from Layout
C++11 Variable Number of Arguments, Same Specific Type
C++ Format MACro/Inline Ostringstream
Boost-Python How to Pass a C++ Class Instance to a Python Class
Convert Eigen Matrix to C Array
_Attribute_((Weak)) and Static Libraries
Cc1Plus: Error: Unrecognized Command Line Option "-Std=C++11" with G++