1

I'm trying to use std::hash_map to define a nested object. I'm using Visual Studio 2013.

The trouble starts when I try to use nested initialization literals. I've reduced my approach to the following:

enum ENUM1 {
    ENUM1_A,
    ENUM1_B,
};

enum ENUM2 {
    ENUM2_A,
    ENUM2_B
};

enum ENUM3 {
    ENUM3_A,
    ENUM3_B
};

std::hash_map<ENUM1, std::hash_map<ENUM2, std::hash_map<ENUM3, int>>> A = {
    {
        ENUM1_A, {
            {   
                ENUM2_A, {
                    { ENUM3_A, 123 },
                    { ENUM3_B, 45 },
                },
            },
            {
                ENUM2_B, {
                    { ENUM3_A, 733 },
                    { ENUM3_B, 413 },
                }
            }
        }
    }
};

The compiler doesn't complain, but on execution it results in an access violation and the following call stack:

    ***.exe!std::list<std::pair<enum ENUM3 const ,int>,std::allocator<std::pair<enum ENUM3 const ,int> > >::clear() Line 1503   C++
    ***.exe!std::list<std::pair<enum ENUM3 const ,int>,std::allocator<std::pair<enum ENUM3 const ,int> > >::_Tidy() Line 1884   C++
    ***.exe!std::list<std::pair<enum ENUM3 const ,int>,std::allocator<std::pair<enum ENUM3 const ,int> > >::~list<std::pair<enum ENUM3 const ,int>,std::allocator<std::pair<enum ENUM3 const ,int> > >() Line 1096  C++
    ***.exe!std::_Hash<stdext::_Hmap_traits<enum ENUM3,int,stdext::hash_compare<enum ENUM3,std::less<enum ENUM3> >,std::allocator<std::pair<enum ENUM3 const ,int> >,0> >::~_Hash<stdext::_Hmap_traits<enum ENUM3,int,stdext::hash_compare<enum ENUM3,std::less<enum ENUM3> >,std::allocator<std::pair<enum ENUM3 const ,int> >,0> >() Line 409 C++
    ***.exe!stdext::hash_map<enum ENUM3,int,stdext::hash_compare<enum ENUM3,std::less<enum ENUM3> >,std::allocator<std::pair<enum ENUM3 const ,int> > >::~hash_map<enum ENUM3,int,stdext::hash_compare<enum ENUM3,std::less<enum ENUM3> >,std::allocator<std::pair<enum ENUM3 const ,int> > >() C++
    ***.exe!std::pair<enum ENUM2 const ,stdext::hash_map<enum ENUM3,int,stdext::hash_compare<enum ENUM3,std::less<enum ENUM3> >,std::allocator<std::pair<enum ENUM3 const ,int> > > >::~pair<enum ENUM2 const ,stdext::hash_map<enum ENUM3,int,stdext::hash_compare<enum ENUM3,std::less<enum ENUM3> >,std::allocator<std::pair<enum ENUM3 const ,int> > > >()  C++
    ***.exe!`dynamic initializer for 'A''() Line 76 C++

Unfortunately I'm very new to C++ and don't know what to make of it myself.

I've seen the following question where the answer references a compiler bug, but the linked bug report specifically mentions that this problem is related to string literals.

What am I doing wrong? Somewhat unrelated, what prevents an object with a constant value like this from being calculated at compile time? Is there a better way to define JSON-like objects in the code?

Community
  • 1
  • 1
user1421750
  • 1,200
  • 2
  • 9
  • 16

1 Answers1

4

There are numerous (well, at least 3) intrusive bugs related to initializer lists and uniform initialization in MSVC2013.

Update According to the comments, this particular bug was removed in VS13 Update 2.

Sadly, the advice is to... stay away from many of them for now. I keep the following rule in mind:

  • on any type that has a constructor taking an initializer list (like all standard containers) always explicitly name the type (IOW don't use anonymous uniform initializer syntax)

Connect bugs:

For what it's worth, the following standard library code is fair game on gcc/clang: Live On Coliru

And here's how I'd recommend wording it for MSVC (I can't test it right now as I don't have a windows box handy):

#include <unordered_map>

enum ENUM1 { ENUM1_A, ENUM1_B }; 
enum ENUM2 { ENUM2_A, ENUM2_B }; 
enum ENUM3 { ENUM3_A, ENUM3_B }; 
namespace std {
    template <> struct hash<ENUM1> : std::hash<int> {};
    template <> struct hash<ENUM2> : std::hash<int> {};
    template <> struct hash<ENUM3> : std::hash<int> {};
}

int main() {
    using Map3 = std::unordered_map<ENUM3, int>;
    using Map2 = std::unordered_map<ENUM2, Map3>;
    std::unordered_map<ENUM1, Map2> A = {
        {
            ENUM1_A, Map2 {
                {
                    ENUM2_A, Map3 {
                        { ENUM3_A, 123 },
                        { ENUM3_B, 45 },
                    },
                },
                {
                    ENUM2_B, Map3 {
                        { ENUM3_A, 733 },
                        { ENUM3_B, 413 },
                    }
                }
            }
        }
    };
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Added a sample of explicit type-name construction for completeness – sehe May 06 '14 at 22:03
  • Thank you for the detailed answer. For reference your proposed workaround is still no-go. – user1421750 May 06 '14 at 23:06
  • @user1421750 Oh, that's mildly surprising. Did you use `std::unordered_map` or the `hash_map` extension? – sehe May 06 '14 at 23:19
  • I used `std::unordered_map`. However I just upgraded to the release candidate of VS13 Update 2, and I'm very pleased to report that the bug has been fixed! My original code works too now. Maybe you can update your answer with that. – user1421750 May 07 '14 at 00:18