
1. Initialization with Braces
Using braces for initialization, specifically with std::initializer_list for list initialization, makes the entire program clearer and simpler to implement. However, various situations can arise during this implementation. Although a temporary array is generated for data storage in general, the details of its implementation differ in various cases. With the evolution of the C++ standard, some implementations have been further optimized. The static storage discussed in this article is one such example.
2. Existing Issues
As mentioned, different situations have different handling mechanisms. Let’s compare the following two implementations:
struct X {
X(std::initializer_list<double> v);
};
X x{ 1,2,3 };
In this case, the expansion essentially stores the related array data (1, 2, 3) in the static storage area. This results in better efficiency and memory usage. However, consider this example:
void f(std::initializer_list<int> il);
void g() {
f({1,2,3});
}
int main() { g(); }
In this case, the expanded temporary array may not necessarily be stored in the static area. Focus on the implementation of the function f, as shown in the following code:
void g();
void f(std::initializer_list<int> il) {
static const int *ptr = nullptr;
if (ptr == nullptr) {
ptr = il.begin();
g();
} else {
assert(ptr != il.begin());
}
}
This is because the standard (C++23 and earlier) requires that two calls (the first call to f and the callback to f in g) should have different addresses. Therefore, the two expanded temporary arrays (1, 2, 3) cannot be stored in static storage. Similarly, similar situations can occur in implementations like STL containers:
std::vector<int> vec = {6,7,8,9,10};
In this case, the data undergoes two copies: from static storage to the stack, and then from the stack to the heap. While this may not be a significant cost for small data sizes, what if a developer wants to store image data in a vector?
3. How to Solve
To address the above issues, prior to C++26, developers could use the previously learned #embed method to embed relevant binary data and prevent data copying, as follows:
static const char backing[] = {
#embed "2mb-image.png"
};
std::vector<char> v = std::vector<char>(backing, std::end(backing));
In C++26, it is proposed to use:
std::initializer_list<int> i1 = {
#embed "very-large-file.png" // Allowed
};
void f2(std::initializer_list<int> ia,
std::initializer_list<int> ib) {
PERMIT(ia.begin() == ib.begin()); // Allow ia.begin() == ib.begin()
}
int main() {
f2({1,2,3}, {1,2,3});
}
// Or as shown in the following code
const char *p1 = "hello world";
const char *p2 = "world";
PERMIT(p2 == p1 + 6); // Current behavior
std::initializer_list<int> i1 = {1,2,3,4,5};
std::initializer_list<int> i2 = {2,3,4};
PERMIT(i1.begin() == i2.begin() + 1); // Proposed future behavior
This new standard aims to prevent constant initialization lists from occupying stack space. In other words, during list initialization, regardless of how many times the array is copied, they can be shared, thus reducing the number of copies and significantly improving efficiency.
4. Conclusion
For developers, the more important aspect of this article is the explanation of the internal details of the C++ standard implementation. It represents an update to the safety and efficiency mechanisms of C++. This may not significantly impact developers, as many programmers are not very sensitive to memory waste and multiple copies when writing code. However, from the standard’s perspective, application developers can handle it freely, but the underlying foundational architecture cannot be treated this way; otherwise, it may seem that their skills are not keeping up.As mentioned earlier, the best materials and examples for developers to learn from are the implementations of the standard, as they are the closest to the perfect implementation of the C++ standard.