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_array is marked constexpr, the array is constructed entirely at compile time.
  • Order-independent: You can pass the keys in any order (e.g., bitbucket before github).
  • Default Initialization: Omitted indices (like index 2 and 3) are default-initialized to empty std::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.