How to Mimic Array Designated Initializers in Modern C++
If you are coming from C or using GCC/Clang extensions, you might be familiar with array designated initializers. They allow you to initialize specific indices of an array directly, like this:
// C-style / GNU Extension (often fails in standard C++)
constexpr std::string_view ServicesNames[] = {
[bitbucket] = "https://bitbucket.org/.*",
[github] = "https://github.com/.*",
[gitlab] = "https://gitlab.com/.*",
};
However, when you try this in standard C++ with non-trivial types (such as std::string_view), you will likely encounter compiler errors like:
sorry, unimplemented: non-trivial designated initializers not supported
error: designator order for field class does not match declaration order
C++20 introduced designated initializers, but only for class member initialization, not for array indices. Furthermore, out-of-order initialization is strictly prohibited in standard C++ designated initializers.
Fortunately, with modern C++ (C++20, C++23, and C++26), we can easily mimic this behavior with zero runtime overhead using constexpr helper functions and std::array. Here are the best ways to achieve this.
Solution 1: The constexpr Sparse Array Builder (Recommended)
The cleanest way to mimic this behavior is to write a helper function that accepts an initializer list of key-value pairs (using your enum as the key) and constructs a fully populated std::array at compile time. Any omitted indices will automatically receive their default-initialized values.
#include <iostream>
#include <array>
#include <string_view>
#include <utility>
enum Services : size_t {
github = 0,
gitlab = 1,
bitbucket = 4,
max_services = 5 // Track the size needed
};
template <typename ValueType, size_t Size, typename KeyType>
constexpr auto make_sparse_array(std::initializer_list<std::pair<KeyType, ValueType>> init) {
std::array<ValueType, Size> arr{};
for (const auto& [key, val] : init) {
arr[static_cast<size_t>(key)] = val;
}
return arr;
}
// Usage:
constexpr auto ServicesNames = make_sparse_array<std::string_view, max_services, Services>({
{bitbucket, "https://bitbucket.org/.*"},
{github, "https://github.com/.*"},
{gitlab, "https://gitlab.com/.*"}
});
int main() {
static_assert(ServicesNames[github] == "https://github.com/.*");
static_assert(ServicesNames[2] == ""); // Default-initialized empty string_view
static_assert(ServicesNames[bitbucket] == "https://bitbucket.org/.*");
std::cout << "Successfully initialized at compile-time!\n";
}
Why this works:
- Compile-time execution: Because
make_sparse_arrayis markedconstexpr, the array is constructed entirely at compile time. - Order-independent: You can pass the keys in any order (e.g.,
bitbucketbeforegithub). - Default Initialization: Omitted indices (like index
2and3) are default-initialized to emptystd::string_views.
Solution 2: Variadic Templates (Zero-Overhead Compile-Time Fold)
If you want to avoid std::initializer_list completely to ensure maximum optimization potential in older compilers, you can use variadic templates and fold expressions:
#include <array>
#include <string_view>
template <typename T, size_t Size, typename... Pairs>
constexpr auto make_sparse_array_variadic(Pairs&&... pairs) {
std::array<T, Size> arr{};
((arr[static_cast<size_t>(pairs.first)] = pairs.second), ...);
return arr;
}
constexpr auto ServicesNames = make_sparse_array_variadic<std::string_view, 5>(
std::pair{bitbucket, "https://bitbucket.org/.*"},
std::pair{github, "https://github.com/.*"},
std::pair{gitlab, "https://gitlab.com/.*"}
);
This approach uses C++17 fold expressions (((arr[...] = ...), ...)) to expand the arguments and assign them to the array. It is extremely fast to compile and results in highly optimized assembly.
What if your Enum is Extremely Sparse?
If your enum values are 0, 1, and 100000, creating a std::array of size 100001 is highly inefficient because of the memory footprint. In this case, you should look into a constexpr flat map lookup instead of a direct array index lookup:
#include <algorithm>
#include <array>
#include <optional>
template <typename Key, typename Value, size_t N>
struct ConstexprMap {
std::array<std::pair<Key, Value>, N> data;
constexpr std::optional<Value> at(Key key) const {
for (const auto& [k, v] : data) {
if (k == key) return v;
}
return std::nullopt;
}
};
constexpr ConstexprMap<Services, std::string_view, 3> ServicesMap = {{
{github, "https://github.com/.*"},
{gitlab, "https://gitlab.com/.*"},
{bitbucket, "https://bitbucket.org/.*"}
}};
// Usage:
constexpr auto url = ServicesMap.at(github).value_or("");
Conclusion
While C++ does not natively support C-style array designated initializers, modern C++ features like constexpr, std::array, and fold expressions make it incredibly easy to mimic this behavior safely, cleanly, and with identical performance characteristics.