I'm calculating some data at compile-time using std::vector
and want to return the results as an array, so it can be used further at run-time. I'm having trouble setting the array size without doing the calculation twice.
Here's a simplified example of what I've done so far. The code compiles and works as expected.
constexpr auto make_vector() {
// complex calculation here
return std::vector{1, 2, 3};
}
constexpr auto make_array() {
const auto vec = make_vector();
std::array<int, make_vector().size()> result{};
std::copy(vec.cbegin(), vec.cend(), result.begin());
return result;
}
int main() {
constexpr auto result = make_array(); // std::array<int, 3>{1, 2, 3}
}
I understand why it's not possible to use vec.size()
for the array size and why make_vector().size()
produces a compile-time constant. It just doesn't seem the right approach to do it twice.
Is there a way to avoid calling make_vector
twice? Am I missing a basic concept here?
Is there a way to avoid calling
make_vector
twice? Am I missing a basic concept here?
Not until C++26 at least. The fundamental issue is that std::vector
allocates memory, so you cannot simply store it in a constexpr
variable (despite it being a literal type) so that its size and contents can be used arbitrarily. A std::vector
can only live temporarily during a constant expression(1).
During this first constant expression, the .size()
is not a constant expression, and there's no way to "elevate it to that status".
Therefore, you don't know the size of the array you're creating, and the vector contents are wasted the first time(2).
This issue is also the motivation in P0784: More constexpr containers(3):
Amongst other things, the lack of variable size [constexpr] containers forces them to use primitive fixed-size data structures in the implementation, and to parse the input JSON string twice; once to determine the size of the data structures, and once to parse the JSON into those structures.
You're not the first to run into this problem, and you'll just have to be patient until the C++ committee and compiler developers figure this one out.
(1) The reason is that even though std::vector
can use new
in constant expressions, all memory must be de-allocated before the end of the constant expression. This is referred to as "transient allocations".
(2) As commenter @HolyBlackCat has pointed out, it's possible that your compiler memoizes the call to make_vector()
so that even though you call it twice, the function doesn't need to be evaluated twice.
(3) This paper has been accepted into C++20, but does not yet solve the issue with wasted container creation.
There is a good chance that your problem will be solved to some extent in C++26, assuming P3032: Less transient constexpr allocation (and more consteval relaxation) is accepted. With this proposal, you could write:
consteval auto make_array() {
constexpr auto vec = make_vector();
std::array<int, make_vector().size()> result;
std::ranges::copy(vec, result.begin());
return result;
}
The solution relies on the fact that the allocation done by make_vector()
doesn't escape the surrounding constant expression because make_array()
is consteval
.
No memory is actually "leaked" to run-time, so vec
can be constexpr
, giving us access to both the data and the size.
An older paper addressing the issue more completely is P1974: Non-transient constexpr allocation. The key issue is that any T*
allocated at compile time and stored in a constexpr
variable (directly, or through a container) can point to mutable memory. This constness issue means that a constexpr std::vector<std::string>
would contain a mixture of mutable/immutable pointers (akin to char ** const
), and that breaks the assumption that we can omit delete
entirely and keep static memory instead. Refer to the paper for more details.