Không thể sử dụng lớp enum làm khóa unirdered_map


77

Tôi có một lớp chứa một lớp enum.

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

Sau đó, khi tôi triển khai mã sau trong một lớp khác ...

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

... Tôi gặp lỗi biên dịch.

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

Điều gì gây ra lỗi ở đây?


6
Bạn không chuyên std::hashvề loại enum.
Kerrek SB

Câu trả lời:


116

Tôi sử dụng một đối tượng functor để tính toán băm của enum class:

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

Bây giờ bạn có thể sử dụng nó làm tham số mẫu thứ 3 của std::unordered_map:

enum class MyEnum {};

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

Vì vậy, bạn không cần phải cung cấp một chuyên môn hóa std::hash, suy luận đối số mẫu thực hiện công việc. Hơn nữa, bạn có thể sử dụng từ usingvà tự unordered_mapsử dụng từ này std::hashhoặc EnumClassHashtùy thuộc vào Keyloại:

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>>;

Bây giờ bạn có thể sử dụng MyUnorderedMapvới enum classhoặc một loại khác:

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

Về mặt lý thuyết, HashTypecó thể sử dụng std::underlying_typevà sau đó EnumClassHashsẽ không cần thiết. Đó có thể là một cái gì đó như thế này, nhưng tôi chưa thử :

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

Nếu sử dụng std::underlying_typecác công trình, có thể là một đề xuất rất tốt cho tiêu chuẩn.


1
Có thể đơn giản nhất, nhưng chỉ nhận được các khóa enum hoạt động ở nơi đầu tiên phải đơn giản hơn? : -S
Jonny

"Về mặt lý thuyết", không, underlying_typesẽ không hoạt động. Bạn đã cho mình thấy: phải có một hash()hàm nhận MyEnumClasstham số. Vì vậy, tất nhiên, hashviệc nhập vào underlying_typesẽ gọi một hàm mong đợi một int(hoặc : yourEnumClassType). Chỉ cần thử underlying_typesẽ cho thấy rằng nó cung cấp chính xác cùng một lỗi: không thể chuyển đổi MyEnumClassthành int. Nếu chỉ đi qua underlying_type đã làm việc, nên sẽ đi MyEnumClasstrực tiếp ở nơi đầu tiên. Dù sao, như David S cho thấy, điều này hiện đã được WG khắc phục. Nếu chỉ GCC sẽ bao giờ phát hành bản vá của họ ...
underscore_d

Điều này không hiệu quả với tôi trong trường hợp lớp enum là một "thành viên" được bảo vệ của một lớp khác. Tôi cần di chuyển định nghĩa enum ra khỏi lớp trực tiếp bên trong định nghĩa không gian tên.
Martin Pecka

1
vì một số lý do, tôi không gặp vấn đề gì khi sử dụng lớp enum làm khóa aa trong bản đồ không có thứ tự. tôi sử dụng clang, có thể hỗ trợ phụ thuộc vào trình biên dịch? chỉnh sửa: như một câu trả lời khác đã chỉ ra, đây là tiêu chuẩn của c ++ 14
johnbakers

1
Câu trả lời được chấp nhận nên được thay đổi để trỏ đến câu trả lời bắt đầu bằng cách chỉ ra rằng hành vi này được coi là một khiếm khuyết trong tiêu chuẩn và đã được sửa trong các trình biên dịch hiện đại.
Michael Allwright

54

Đây được coi là một khiếm khuyết trong tiêu chuẩn và đã được sửa trong C ++ 14: http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2148

Điều này đã được khắc phục trong phiên bản vận chuyển libstdc ++ với gcc kể từ 6.1: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60970 .

Nó đã được sửa trong libc ++ của clang vào năm 2013: http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20130902/087778.html


1
Tôi ngạc nhiên enum classsử dụng như chìa khóa trong std::unordered_setbiên dịch cho Visual Studio 2013.
Richard Dally

3
@ypnos: Rất có thể đó là một sự cố ý sửa chữa. Nó tin rằng điều này không hoạt động trong Visual Studio 2012, vì vậy họ có thể đã sửa nó vào năm 2013 như một phần của lỗi đó. Trên thực tế, STL, người bảo trì thư viện chuẩn C ++ tại Microsoft, là người đã cung cấp từ ngữ để giải quyết lỗi.
David Stone,

1
@ypnos: Tôi thích cách tiếp cận Visual Studio là chỉ đưa mọi người vào phiên bản mới nhất của ngôn ngữ, vì C ++ C ++ 14 ngay bây giờ.
David Stone

2
@DavidStone: Trên thực tế, Visual Studio hiện tại không hỗ trợ C ++ 14 hoặc thậm chí là C ++ 11. Cả hai đều bao gồm C99, không được Visual Studio hỗ trợ. Tôi không nói VS không nên mặc định là phiên bản ngôn ngữ mới nhất. Tôi đang nói rằng nó không nên giới thiệu tiêu chuẩn de-facto của riêng mình khi có một số tiêu chuẩn nhất định được hỗ trợ bởi tất cả các trình biên dịch cạnh tranh. VS 2013 ra mắt một năm trước khi C ++ 14 được hoàn thiện. Tuy nhiên, thay vì hỗ trợ đầy đủ C ++ 11, nó bao gồm một tập hợp con các tính năng C ++ 14.
ypnos

2
Hãy tin tôi, tôi không chỉ ném một liên kết ở anh- Đầu tiên, trang web không mô tả C ++ 14, chỉ cần di chuyển lên. Thứ hai, "Hỗ trợ tối thiểu cho việc thu gom rác" tuân thủ các tiêu chuẩn. Nếu bạn đọc thông số kỹ thuật (được liên kết ở đó!): "Việc triển khai không hỗ trợ thu thập rác và thực hiện tất cả các lệnh gọi thư viện được mô tả ở đây là no-ops đang tuân thủ." Đây là những gì GCC làm. Và tôi không thấy cách các nền tảng bạn viết mã hoặc trình biên dịch nào bạn cảm thấy thoải mái thêm bất cứ điều gì vào cuộc thảo luận, đó là về sự tuân thủ các tiêu chuẩn chứ không phải về chất lượng trình biên dịch được nhận thức riêng.
ypnos

22

Một giải pháp rất đơn giản là cung cấp một đối tượng hàm băm như sau:

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

Đó là tất cả đối với một khóa enum, không cần cung cấp chuyên môn hóa std :: hash.


27
Điều này phù hợp với các enum lâu đời, nhưng không phù hợp với các "lớp enum" mới như OP đang sử dụng.
BrandonLWhite

5
Làm thế nào mà nó đạt được +8 khi nó ngang nhiên không trả lời câu hỏi?
underscore_d

^ Chà, theo câu trả lời của Vladimir bên dưới, có thể denim đã thử nghiệm điều này trong VS2012 hoặc một số trình biên dịch khác bằng cách nào đó cho phép điều này.
underscore_d

6

Thêm điều này vào tiêu đề xác định MyEnumClass:

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

1
Bạn không nên thêm const noexceptvào chữ ký?
einpoklum

Không stdmay là gia hạn là hành vi không xác định.
Victor Polevoy

3
@VictorPolevoy: mở rộng std: may mắn là hành vi được định nghĩa, đối với trường hợp đặc biệt này. en.cppreference.com/w/cpp/language/exfining_std
galinette

Tôi nghĩ llvm 8.1 có vấn đề với điều này nhưng trên các trình biên dịch khác hoạt động tốt.
Moshe Rabaev

5

Như KerrekSB đã chỉ ra, bạn cần cung cấp chuyên môn std::hashnếu muốn sử dụng std::unordered_map, chẳng hạn như:

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

Khi bạn sử dụng std::unordered_map, bạn biết bạn cần một hàm băm. Đối với các STLloại hoặc cài sẵn, có sẵn các giá trị mặc định, nhưng không có các giá trị mặc định do người dùng xác định. Nếu bạn chỉ cần một tấm bản đồ, tại sao bạn không thử std::map?


29
std::unordered_mapcó hiệu suất vượt trội trong hầu hết các tình huống, và có lẽ nên được coi là mặc định nhiều hơn std::map.
David Stone

-1

Thử

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

Sẽ không hiệu quả. Điều đó std::hash()sẽ mong đợi một trường hợp underlying_typedưới dạng tham số nhưng sẽ nhận được MyEnumClassthay thế. Đây chính xác là điều xảy ra khi bạn cố gắng sử dụng enumgiải pháp đơn giản cũ là chỉ định std::hash<int>. Đã bạn thử điều này trước khi đưa ra nó?
underscore_d

chắc chắn, tôi đã làm. Biên dịch tốt trong VS 2012. Chính xác đây là "không gian tên ObjectDefines {enum ObjectType {ObjectHigh, ....}} std :: unardered_map <ObjectDefines :: ObjectType, ObjectData *, std :: hash <std :: underlying_type <ObjectDefines :: ObjectType > :: loại >> m_mapEntry; "
Vladimir Shutow

Câu hỏi là về enum class, không phải C-style unscoped enums. Bạn sẽ thấy rằng nhận xét của tôi là đúng cho enum class, đó là chủ đề.
underscore_d

Tôi thấy sự khác biệt. Nhưng nó vẫn biên dịch tốt: class ObjectDefines {public: enum class ObjectType {ObjectHigh, ObjectLow}; }; std :: unardered_map <ObjectDefines :: ObjectType, ObjectDefines *, std :: hash <std :: underlying_type <ObjectDefines :: ObjectType> :: type >> m_mapEntry;
Vladimir Shutow

1
trong gcc 4.7.3 nó cũng biên dịch (kiểm tra tại đây melpon.org/wandbox/permlink/k2FopvmxQeQczKtE )
Vladimir Shutow
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.