Use std::type_index as value in a map

4k views Asked by At

I am trying to create a std::unordered_map where the value is a std::type_index. The following snippet works:

std::unordered_map<std::type_index, int> workingMap;
workingMap[typeid(int)] = 1;
workingMap[typeid(char)] = 2;

But this one doesn't run and throws an error:

std::unordered_map<int, std::type_index> failingMap;
failingMap[1] = typeid(int);
failingMap[2] = typeid(char);

CS2512: 'std::type_index::type_index': no appropriate default constructor available.

I don't fully understand this error, what is the difference between the constructors in these examples? Is it possible to make a map where typeid(..) is the value instead of the key?

2

There are 2 answers

0
Alex Huszagh On BEST ANSWER

The issue is operator[], not the actual use of the map. The problem is if the key is not found, operator[] will assign a default value and return a modifiable reference to that value, which is impossible with std::type_index. You may use emplace, insert, try_emplace, or any other modifier that does not require a default constructor.

1
Richard Hodges On

We could of course always create a nullable wrapper for type_info.

#include <typeindex>
#include <functional>
#include <unordered_map>
#include <cassert>


struct nullable_type_index
{
    constexpr nullable_type_index() : ptr_(nullptr) {}
    constexpr nullable_type_index(std::type_info const& ti) : ptr_(std::addressof(ti)) {}

    constexpr operator bool() const
    {
        return bool(ptr_);
    }

    // I have avoided implicit conversion, but it could probably work
    // without any surprises.
    std::type_info const& get_type_info() const {
        assert(ptr_);
        return *ptr_;
    }

    constexpr bool operator==(nullable_type_index const& other) const {
        return ptr_ && other.ptr_ 
        ? *ptr_ == *other.ptr_
        : ptr_ == other.ptr_;
    }

private:
    std::type_info const* ptr_;
};

std::size_t hash_value(const nullable_type_index& nti)
{
    return nti ? 0 : nti.get_type_info().hash_code();
}

bool operator==(nullable_type_index const& l, std::type_info const& r)
{
    return l == nullable_type_index(r);
}

bool operator==(std::type_info const& l, nullable_type_index const& r)
{
    return nullable_type_index(l) == r;
}

namespace std {

    template<>
    struct hash<nullable_type_index>
    {
        std::size_t operator()(nullable_type_index const& arg) const {
            return hash_value(arg);
        }
    };
}

int main()
{
    std::unordered_map<std::type_index, int> workingMap;
    workingMap[typeid(int)] = 1;
    workingMap[typeid(char)] = 2;    

    std::unordered_map<int, nullable_type_index> failingMap;
    failingMap[1] = typeid(int);
    failingMap[2] = typeid(char);
}

or of course, now we have std::optional...

int main()
{
    std::unordered_map<std::type_index, int> workingMap;
    workingMap[typeid(int)] = 1;
    workingMap[typeid(char)] = 2;    

    std::unordered_map<int, std::optional<std::type_index>> failingMap;
    failingMap[1] = typeid(int);
    failingMap[2] = typeid(char);
}