Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to inplace initialize std::array<std::pair<const Key, Val>>?

Tags:

c++

stl

I am trying to create a static map for where the type of the key is an enum, and the values and keys are stored inside an std::array.

The below code works nicely for trivially copiable types, but unfortunately fails for types such as std::mutex and std::atomic<T>.

#pragma once
#include <magic_enum.hpp>
#include <bitset>
#include <array>

template <typename Key, typename T>
struct EnumMap {
    static_assert(std::is_enum<Key>::value);

    using key_type = Key;
    using mapped_type = T;
    using value_type = std::pair<const key_type, mapped_type>;
    using size_type = std::size_t;


    EnumMap() : data_(initializeData()){
    }
private:
    static constexpr size_type enum_size_ = magic_enum::enum_count<Key>();
    std::array<value_type, enum_size_> data_;
    std::bitset<enum_size_> filled_;

    constexpr auto initializeData() {
        return initializeDataHelper(std::make_index_sequence<enum_size_>());
    }

    template <size_type... Is>
    constexpr auto initializeDataHelper(std::index_sequence<Is...>) {
        constexpr auto enum_entries = magic_enum::enum_values<Key>();
        return std::array<value_type, enum_size_>{{std::make_pair(enum_entries[Is], T{})... }};
    }
};

Can anyone think of a version where std::mutex would work? I am guessing placement new could be used for this, but perhaps there is a nicer index_sequence based solution also.

like image 906
valikund Avatar asked Dec 31 '25 06:12

valikund


2 Answers

For the uninitiated to the black magic performed by @Passer By, std::pair also offers a special constructor using tuples, allowing you to generate a potentially empty parameter pack:

template< class... Args1, class... Args2 >
pair( std::piecewise_construct_t,
      std::tuple<Args1...> first_args,
      std::tuple<Args2...> second_args );

This allows you to explicitly pass zero parameters, i.e.

return std::array<value_type, enum_size_>{ value_type(std::piecewise_construct, std::make_tuple(enum_entries[Is]), std::make_tuple())...};

This is also extendable to the non-default constructible types:

template<typename... Args>
EnumMap(Args&&... args) : data_(initializeData(std::make_tuple(std::forward<Args>(args)...))) {}

template<typename Init>
constexpr auto initializeData(Init&& init) {
    return initializeDataHelper(
        std::make_index_sequence<enum_size_>(),
        std::forward<Init>(init)
    );
}

template <size_type... Is, typename Init>
constexpr auto initializeDataHelper(std::index_sequence<Is...>, Init&& init) {
    constexpr auto enum_entries = magic_enum::enum_values<Key>();
    return std::array<value_type, enum_size_>{
        value_type(std::piecewise_construct, std::make_tuple(enum_entries[Is]), std::forward<Init>(init))...
    };
}

Same example: https://godbolt.org/z/dEEWqo3v1

like image 163
Cedric Avatar answered Jan 03 '26 11:01

Cedric


You abuse casts

template<typename T>
struct type_identity
{
    using type = T;
};

template<typename F>
struct initializer
{
    F f;
    template<typename T>
    operator T() &&
    {
        return std::forward<F>(f)(type_identity<T>{});
    }
};

template<typename F>
initializer(F&&) -> initializer<F>;

template<typename... Args>
auto initpack(Args&&... args)
{
    return initializer{[&](auto t) {
        using Ret = typename decltype(t)::type;
        return Ret{std::forward<Args>(args)...};
    }};
}

Then write

return std::array<value_type, enum_size_>{std::make_pair(enum_entries[Is], initpack())...};

You can extend this to non-default constructible types

template<typename... Args>
EnumMap(Args&&... args) : data_(initializeData(initpack(args...))) {}

template<typename Init>
constexpr auto initializeData(Init&& init) {
    return initializeDataHelper(
        std::make_index_sequence<enum_size_>(),
        std::forward<Init>(init)
    );
}

template <size_type... Is, typename Init>
constexpr auto initializeDataHelper(std::index_sequence<Is...>, Init&& init) {
    constexpr auto enum_entries = magic_enum::enum_values<Key>();
    return std::array<value_type, enum_size_>{
        std::make_pair(enum_entries[Is], std::forward<Init>(init))...
    };
}

Live.

like image 24
Passer By Avatar answered Jan 03 '26 12:01

Passer By