Невозможно использовать класс перечисления в качестве ключа unordered_map

У меня есть класс, содержащий класс enum.

class Shader {
public:
    enum class Type {
        Vertex   = GL_VERTEX_SHADER,
        Geometry = GL_GEOMETRY_SHADER,
        Fragment = GL_FRAGMENT_SHADER
    };
    //...

Затем, когда я реализую следующий код в другом классе...

std::unordered_map<Shader::Type, Shader> shaders;

... Я получаю ошибку компиляции.

...usr/lib/c++/v1/type_traits:770:38: 
Implicit instantiation of undefined template 'std::__1::hash<Shader::Type>'

Что вызывает ошибку здесь?


person Appleshell    schedule 16.09.2013    source источник
comment
Вы не специализировали std::hash для типа перечисления.   -  person Kerrek SB    schedule 17.09.2013


Ответы (7)


Я использую объект функтора для вычисления хэша enum class:

struct EnumClassHash
{
    template <typename T>
    std::size_t operator()(T t) const
    {
        return static_cast<std::size_t>(t);
    }
};

Теперь вы можете использовать его как третий параметр шаблона std::unordered_map:

enum class MyEnum {};

std::unordered_map<MyEnum, int, EnumClassHash> myMap;

Таким образом, вам не нужно предоставлять специализацию std::hash, вывод аргумента шаблона делает свою работу. Кроме того, вы можете использовать слово using и создавать свои собственные unordered_map, которые используют std::hash или EnumClassHash в зависимости от типа Key:

template <typename Key>
using HashType = typename std::conditional<std::is_enum<Key>::value, EnumClassHash, std::hash<Key>>::type;

template <typename Key, typename T>
using MyUnorderedMap = std::unordered_map<Key, T, HashType<Key>>;

Теперь вы можете использовать MyUnorderedMap с enum class или другим типом:

MyUnorderedMap<int, int> myMap2;
MyUnorderedMap<MyEnum, int> myMap3;

Теоретически HashType мог бы использовать std::underlying_type и тогда EnumClassHash не понадобится. Это может быть что-то вроде этого, но я еще не пробовал:

template <typename Key>
using HashType = typename std::conditional<std::is_enum<Key>::value, std::hash<std::underlying_type<Key>::type>, std::hash<Key>>::type;

Если использование std::underlying_type работает, это может быть очень хорошим предложением для стандарта.

person Daniel    schedule 20.07.2014
comment
Может быть, проще всего, но просто заставить работать ключи enum должно быть проще? :-С - person Jonny; 04.01.2016
comment
Теоретически нет, underlying_type не получится. Вы показали себя: должна быть функция hash(), которая принимает параметр MyEnumClass. Так что, конечно, hashing на underlying_type вызывает функцию, которая ожидает int (или : yourEnumClassType). Просто попытка underlying_type показала бы, что она дает точно такую ​​же ошибку: невозможно преобразовать MyEnumClass в int. Если бы просто передача underlying_type действительно работала, поэтому в первую очередь передавалась бы MyEnumClass напрямую. В любом случае, как показывает Дэвид С., это уже исправлено рабочей группой. Если бы только GCC выпустили свой патч... - person underscore_d; 11.01.2016
comment
Это не сработало для меня, если класс enum был защищенным членом другого класса. Мне нужно было переместить определение enum из класса непосредственно в определение пространства имен. - person Martin Pecka; 21.03.2017
comment
по некоторым причинам у меня вообще нет проблем с использованием класса enum в качестве ключа в неупорядоченной карте. я использую clang, возможно, поддержка зависит от компилятора? редактировать: как указывает другой ответ, это стандарт с С++ 14 - person johnbakers; 02.05.2017
comment
Принятый ответ следует изменить, чтобы указать на ответ, который начинается с указания на то, что такое поведение считается дефектом стандарта и исправлено в современных компиляторах. - person mallwright; 03.10.2019
comment
Это решение плохое, так как без необходимости создает копию ключа. Это вызовет проблемы с производительностью, если вы не используете ссылку. - person nikc; 06.08.2020
comment
У меня есть опасения, что если 2 или более перечислений определены как имеющие одинаковое значение, это вызовет логическую проблему. Это правильно? - person didinino; 09.08.2020

Это считалось дефектом в стандарте и было исправлено в C++14: http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2148.

Это исправлено в версии libstdc++, поставляемой с gcc начиная с 6.1: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60970.

Это было исправлено в libc++ Clang в 2013 году: http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20130902/087778.html

person David Stone    schedule 14.04.2015
comment
Я удивлен, что enum class используется в качестве ключа в std::unordered_set компиляциях для Visual Studio 2013. - person Richard Dally; 10.08.2015
comment
Я не удивлен. Всякое нестандартное дерьмо компилируется в Visual Studio, так было испокон веков. - person ypnos; 21.08.2015
comment
@ypnos: Скорее всего, это было преднамеренное исправление. Считается, что это не работает в Visual Studio 2012, поэтому они, вероятно, исправили это в 2013 году как часть этого дефекта. На самом деле STL, специалист по сопровождению стандартной библиотеки C++ в Microsoft, предоставил формулировку для устранения дефекта. - person David Stone; 21.08.2015
comment
Я не говорю, что это плохо, что они это исправили или что их компилятор делает случайные вещи. Я просто говорю, что Microsoft всегда обеспечивает некое неопределенное состояние соответствия стандартам, когда одни части новейшего стандарта поддерживаются, другие нет, а некоторые части будущих стандартов поддерживаются раньше. Это значительно усложняет написание стандартного совместимого кода, который также компилируется с другими наборами инструментов. В GCC или clang вы можете указать, для какой стандартной версии вы программируете. Но программисты Windows не говорят, что я программирую на C++-11 или на C89. Говорят, я программирую VS 2012… - person ypnos; 21.08.2015
comment
@ypnos: я предпочитаю подход Visual Studio, заключающийся в том, чтобы просто перевести всех на новейшую версию языка, поскольку C++ является C++14 прямо сейчас. - person David Stone; 23.10.2015
comment
@DavidStone: На самом деле текущая Visual Studio не поддерживает C++14 или даже C++11. Оба они включают C99, который не поддерживается Visual Studio. Я не говорю, что VS не должен по умолчанию использовать новейшую языковую версию. Я говорю, что он не должен вводить свой собственный стандарт де-факто, когда есть определенные доступные стандарты, которые поддерживаются всеми конкурирующими компиляторами. VS 2013 вышел за год до завершения разработки C++14. Тем не менее, вместо полной поддержки C++11 он включает подмножество функций C++14. - person ypnos; 23.10.2015
comment
Ответ @DavidStone Владимира ниже указывает, что VS2012 действительно поддерживает это (или некоторые варианты) в unordered_map, как бы правильно это ни было. - person underscore_d; 12.01.2016
comment
@ypnos Да, ну, как показано выше, gcc также не реализует полный стандарт, так что это черный чайник. - person quant_dev; 22.03.2016
comment
@quant_dev О чем ты говоришь? GCC полностью соответствует всем стандартам C++, см. gcc.gnu.org/projects/ cxx-status.html#cxx11. Хотя разработчикам GCC также нужно время для внедрения новых стандартов, они стремятся к 100%-му подтверждению стандартов, и у них есть опыт достижения этого. Это противоположность Microsoft, которая сознательно не стремится к соответствию стандартам, а, как они говорят, к функциям, которые просят наши клиенты. Кроме того, моя основная мысль заключалась в следующем: в GCC, Clang и т. д. я могу выбрать стандартную версию, с которой я программирую, и получить ожидаемое поведение со всеми достаточно последними версиями компилятора. - person ypnos; 22.03.2016
comment
@ypnos Ваша страница вообще не описывает C ++ 14, и 1 функция C ++ 11 отсутствует (минимальная поддержка сборки мусора и обнаружения утечек на основе доступности N2670 Нет). - person quant_dev; 22.03.2016
comment
И на самом деле, как разработчик C++, который пишет код как для Linux, так и для Windows, я считаю Visual Studio C++ очень хорошим и удобным в использовании компилятором. Где-то хуже, чем в GCC, где-то лучше. - person quant_dev; 22.03.2016
comment
Поверьте, я не просто кинул вам ссылку — во-первых, страница действительно описывает C++14, просто прокрутите вверх. Во-вторых, минимальная поддержка сборки мусора соответствует стандартам. Если вы читали спецификацию (там есть ссылка!): Реализация, которая не поддерживает сборку мусора и реализует все библиотечные вызовы, описанные здесь, как no-ops, соответствует требованиям. Это то, что делает GCC. И я не понимаю, как платформы, для которых вы пишете код, или компилятор, который вы считаете удобным, добавляют что-то к обсуждению, которое касалось соответствия стандартам, а не индивидуально воспринимаемого качества компилятора. - person ypnos; 22.03.2016
comment
В gcc версии 9.1.0 (где функции C++11 и C++14 включены по умолчанию) код компилируется без каких-либо предупреждений или ошибок. - person mallwright; 03.10.2019
comment
@allsey87: ответ обновлен, чтобы отразить, что gcc исправил это в версии 6.1. - person David Stone; 04.10.2019

Очень простым решением было бы предоставить объект хеш-функции следующим образом:

std::unordered_map<Shader::Type, Shader, std::hash<int> > shaders;

Это все, что касается ключа перечисления, нет необходимости указывать специализацию std::hash.

person denim    schedule 12.03.2014
comment
Это работает для устаревших перечислений, но не для новых классов перечислений hotness, которые использует OP. - person BrandonLWhite; 25.04.2014
comment
Как это дошло до +8, если оно откровенно не отвечает на вопрос? - person underscore_d; 11.01.2016
comment
^ Ну, согласно ответу Владимира ниже, возможно, деним протестировал это в VS2012 или каком-то другом компиляторе, который каким-то образом это позволяет. - person underscore_d; 12.01.2016

Добавьте это в заголовок, определяющий MyEnumClass:

namespace std {
  template <> struct hash<MyEnumClass> {
    size_t operator() (const MyEnumClass &t) const { return size_t(t); }
  };
}
person user3080602    schedule 09.02.2016
comment
Разве вы не должны добавить const noexcept к подписи? - person einpoklum; 12.04.2016
comment
К сожалению, расширение std является неопределенным поведением. - person Victor Polevoy; 06.03.2017
comment
@VictorPolevoy: расширение std:, к счастью, определяет поведение для этого особого случая. en.cppreference.com/w/cpp/language/extending_std - person galinette; 20.04.2017
comment
Я думаю, что в llvm 8.1 есть проблема с этим, но на других компиляторах работает нормально. - person Moshe Rabaev; 30.07.2019

Как указал KerrekSB, вам необходимо указать специализацию std::hash, если вы хотите использовать std::unordered_map, например:

namespace std
{
    template<>
    struct hash< ::Shader::Type >
    {
        typedef ::Shader::Type argument_type;
        typedef std::underlying_type< argument_type >::type underlying_type;
        typedef std::hash< underlying_type >::result_type result_type;
        result_type operator()( const argument_type& arg ) const
        {
            std::hash< underlying_type > hasher;
            return hasher( static_cast< underlying_type >( arg ) );
        }
    };
}
person Daniel Frey    schedule 16.09.2013

Когда вы используете std::unordered_map, вы знаете, что вам нужна хеш-функция. Для встроенных или STL типов доступны значения по умолчанию, но не для пользовательских. Если вам просто нужна карта, почему бы вам не попробовать std::map?

person CS Pei    schedule 16.09.2013
comment
std::unordered_map имеет превосходную производительность почти во всех ситуациях, и, вероятно, его следует рассматривать скорее как вариант по умолчанию, чем std::map. - person David Stone; 14.04.2015

Пытаться

std::unordered_map<Shader::Type, Shader, std::hash<std::underlying_type<Shader::Type>::type>> shaders;
person Vladimir Shutow    schedule 15.12.2015
comment
Не сработает. Этот std::hash() будет ожидать экземпляр underlying_type в качестве параметра, но вместо этого получит MyEnumClass. Это точно то же самое, что происходит, когда вы пытаетесь использовать старое простое решение enum с указанием std::hash<int>. Вы пробовали это, прежде чем предложить это? - person underscore_d; 11.01.2016
comment
конечно, я сделал. Отлично компилируется в VS 2012. Именно это пространство имен ObjectDefines { enum ObjectType { ObjectHigh, .... } } std::unordered_map‹ObjectDefines::ObjectType, ObjectData*, std::hash‹std::underlying_type‹ObjectDefines::ObjectType› ::type›› m_mapEntry; - person Vladimir Shutow; 11.01.2016
comment
Вопрос касается enum class, а не enum без границ в стиле C. Вы увидите, что мой комментарий верен для enum class, который является субъектом. - person underscore_d; 11.01.2016
comment
Я вижу разницу. Но все равно компилируется нормально: class ObjectDefines { public: enum class ObjectType { ObjectHigh, ObjectLow}; }; std::unordered_map‹ObjectDefines::ObjectType, ObjectDefines*, std::hash‹std::underlying_type‹ObjectDefines::ObjectType›::type›› m_mapEntry; - person Vladimir Shutow; 11.01.2016
comment
Интересный! Основываясь на других ответах, особенно на том, что упоминается стандартный дефект, я почти уверен, что это не должно работать... (и я не знаю, как бы они это реализовали). мой предыдущий тест, тот же код в GCC (с добавлением #include <unordered_map>) создает классическую кучу шаблонных сообщений об ошибках, из которых первым и наиболее полезным является /usr/include/c++/5/bits/hashtable_policy.h:85:34: error: no match for call to ‘(const std::hash<int>) (const ObjectDefines::ObjectType&)’ . LLVM/clang++ соглашается. Может ли кто-нибудь подтвердить, является ли VS2012 неправильным или правильным, и, возможно, проверить последнюю версию? - person underscore_d; 12.01.2016
comment
в gcc 4.7.3 он тоже компилируется (проверено здесь melpon.org/wandbox/permlink/k2FopvmxQeQczKtE ) - person Vladimir Shutow; 12.01.2016
comment
Да, но не в версии 5.2.0, доступной на той же странице, или в последней версии v4 4.9.2. Это становится еще более странным... - person underscore_d; 12.01.2016