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


77

У мене є клас, що містить клас 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>'

Що спричиняє помилку тут?


6
Ви не спеціалізувались std::hashна переліченні.
Kerrek SB

Відповіді:


116

Я використовую об’єкт функтора для обчислення хешу 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твори, це може бути дуже гарною пропозицією для стандарту.


1
Можливо, найпростіший, але просто отримання ключів перерахування, що працюють в першу чергу, має бути простішим? : -S
Джонні

"Теоретично", ні, underlying_typeне вийде. Ви показали себе: повинна бути hash()функція, яка приймає MyEnumClassпараметр. Отже, звичайно, hashing on underlying_typeвикликає функцію, яка очікує int(або : yourEnumClassType). Просто намагався underlying_typeб я показав , що він дає точно таку ж помилку: не вдається перетворити MyEnumClassв int. Якщо тільки проїздом underlying_type зробив роботу, так що проходить MyEnumClassбезпосередньо в першу чергу. У будь-якому випадку, як показує Девід С., це зараз виправлено РГ. Якби тільки GCC коли-небудь випустив свій патч ...
underscore_d

Це не спрацювало для мене, якщо клас enum був захищеним "членом" іншого класу. Мені потрібно було перемістити визначення переліку з класу безпосередньо всередину визначення простору імен.
Martin Pecka,

1
з якихось причин у мене взагалі не виникає проблем із використанням класу enum як ключа aa в невпорядкованій карті. я використовую clang, можливо підтримка залежить від компілятора? редагувати: як вказує інша відповідь, це входить у стандарт станом на c ++ 14
johnbakers

1
Прийняту відповідь слід змінити, щоб вказати на відповідь, яка починається із зазначення, що така поведінка вважалася дефектом стандарту і була виправлена ​​у сучасних компіляторах.
Michael Allwright

54

Це було визнано дефектом стандарту та виправлено в 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


1
Я здивований, що enum classвикористовується як ключ у std::unordered_setкомпіляціях для Visual Studio 2013.
Річард Даллі,

3
@ypnos: Швидше за все, це було навмисне виправлення. Він вважає, що це не працює у Visual Studio 2012, тому вони, ймовірно, виправили це у 2013 році як частину цього дефекту. Насправді STL, що підтримує стандартну бібліотеку C ++ у Microsoft, є тим, хто надав формулювання для усунення дефекту.
Девід Стоун

1
@ypnos: Я віддаю перевагу підходу Visual Studio - просто покласти всіх на найновішу версію мови, оскільки зараз C ++ - це C ++ 14.
Девід Стоун,

2
@DavidStone: По суті, поточна Visual Studio не підтримує C ++ 14 або навіть C ++ 11. Вони обидва включають C99, який не підтримується Visual Studio. Я не кажу, що VS не повинен використовувати за замовчуванням найновішу мовну версію. Я кажу, що він не повинен вводити власний фактичний стандарт, якщо є певні стандарти, які підтримуються усіма конкуруючими компіляторами. VS 2013 вийшов цілий рік до того, як C ++ 14 був доопрацьований. Однак, замість того, щоб повністю підтримувати C ++ 11, він включає підмножину функцій C ++ 14.
ypnos

2
Повірте, я не просто підкинув вам посилання - по-перше, на сторінці справді описується C ++ 14, просто прокрутіть угору. По-друге, "Мінімальна підтримка збору сміття" відповідає стандартам. Якщо ви прочитали специфікацію (зв’язану там!): "Реалізація, яка не підтримує збір сміття і реалізує всі виклики бібліотеки, описані тут як no-ops, відповідає." Це те, що робить GCC. І я не бачу, як платформи, для яких ви пишете код, чи який компілятор, який вам зручний, додають що-небудь до дискусії, яка стосувалася відповідності стандартам, а не окремо сприйманої якості компілятора.
ypnos

22

Дуже простим рішенням було б надати об’єкт хеш-функції, як це:

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

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


27
Це працює для старих перерахувань, але не для нових "класів перерахування", як використовується OP.
BrandonLWhite

5
Як це дійшло до +8, коли воно відверто не відповідає на питання?
underscore_d

^ Ну, відповідно до відповіді Володимира нижче, можливо, джинсова тканина тестувала це у VS2012 або якомусь іншому компіляторі, який якось це дозволяє.
underscore_d

6

Додайте це до заголовка, що визначає MyEnumClass:

namespace std {
  template <> struct hash<MyEnumClass> {
    size_t operator() (const MyEnumClass &t) const { return size_t(t); }
  };
}

1
Ви не повинні додати const noexceptдо підпису?
einpoklum

На stdжаль, продовження - це невизначена поведінка.
Віктор Польовий

3
@VictorPolevoy: extending std: визначається поведінка, на щастя, для цього особливого випадку. en.cppreference.com/w/cpp/language/extending_std
galinette

Я думаю, що llvm 8.1 має проблему з цим, але на інших компіляторах працює нормально.
Моше Рабаєв

5

Як зазначив 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 ) );
        }
    };
}

5

Коли ви використовуєте std::unordered_map, ви знаєте, що вам потрібна хеш-функція. Для вбудованих або STLтипів доступні за замовчуванням, але не для визначених користувачем. Якщо вам просто потрібна карта, чому б вам не спробувати std::map?


29
std::unordered_mapмає чудові показники майже у всіх ситуаціях, і, мабуть, слід вважати їх за замовчуванням ніж std::map.
Девід Стоун

-1

Спробуйте

std::unordered_map<Shader::Type, Shader, std::hash<std::underlying_type<Shader::Type>::type>> shaders;

Не буде працювати. Це std::hash()буде очікувати екземпляр underlying_typeяк параметр, але MyEnumClassзамість цього отримає . Це точно те саме, що трапляється, коли ви намагаєтесь використати старе звичайне enumрішення зазначення std::hash<int>. Можливо , ви спробуєте це , перш ніж пропонувати його?
underscore_d

звичайно, я зробив. Прекрасно компілюється у VS 2012. Саме це "простір імен ObjectDefines {перелічити ObjectType {ObjectHigh, ....}} std :: unordered_map <ObjectDefines :: ObjectType, ObjectData *, std :: hash <std :: underlying_type <ObjectDefines :: ObjectType > :: type >> m_mapEntry; "
Володимир Шутов

Питання в тому enum class, а не в нерозроблених стилях enumС. Ви побачите, що мій коментар enum classвідповідає темі, яка є темою.
underscore_d

Я бачу різницю. Але він все одно добре компілює: class ObjectDefines {public: enum class ObjectType {ObjectHigh, ObjectLow}; }; std :: unordered_map <ObjectDefines :: ObjectType, ObjectDefines *, std :: hash <std :: underlying_type <ObjectDefines :: ObjectType> :: type >> m_mapEntry;
Володимир Шутов

1
у gcc 4.7.3 він також компілюється (перевірено тут melpon.org/wandbox/permlink/k2FopvmxQeQczKtE )
Володимир Шутов
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.