c++stl

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


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.


Solution

  • 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