Initializing Map of Maps with Initializer List in VS 2013

Initializing map of maps with initializer list in VS 2013

This is a known compiler bug, http://connect.microsoft.com/VisualStudio/feedback/details/800104/ . The compiler gets confused by temporaries in initializer lists, and can even destroy an individual object repeatedly. Because this is silent bad codegen, I've asked the compiler team to prioritize fixing this.

How to use std::map::operator= with initializer lists

You could construct a temporary and use it in the assignment.

std::map<int, char> m;
m = std::map<int, char>{{1, 'a'}, {3, 'b'}, {5, 'c'}, {7, 'd'}};

If you don't want to repeat the type, you can use decltype.

std::map<int, char> m;
m = decltype(m){{1, 'a'}, {3, 'b'}, {5, 'c'}, {7, 'd'}};

Related SO posts:

  • Initializing map of maps with initializer list in VS 2013
  • Using Initializer Lists with std::map
  • Is this a compiler bug? Am I doing something wrong?

Using Initializer Lists with std::map

At Slava's insistence, I worked with ctors to find an easy fix:

#include <map>
#include <string>
#include <iostream>

struct Params
{
int inputType;
std::string moduleName;
Params(const int n, const std::string& s) :
inputType(n),
moduleName(s)
{ }
};

int main()
{
std::map<std::string, Params> options = {
{ "Add", Params(30, "RecordLib" ) },
{ "Open", Params(40, "ViewLib" ) },
{ "Close", Params(50, "EditLib" ) },
{ "Inventory", Params(60, "ControlLib") },
{ "Report", Params(70, "ReportLib" ) }
};

for (const auto& pair : options)
{
std::cout << "Entry: " << pair.first << " ==> { " << pair.second.moduleName << " }" << std::endl;
}

return 0;
}

However, the original code should have worked, and apparently is an acknowledged bug by Microsoft.

Static const map initialization list with struct?

Visual Studio 2013 Update 2 has just fixed this bug.

Nested hash_map initilizer lists

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:

  • https://connect.microsoft.com/VisualStudio/feedback/details/809243/c-11-initializer-lists-as-default-argument

  • https://connect.microsoft.com/VisualStudio/feedback/details/800364/initializer-list-calls-object-destructor-twice

  • http://connect.microsoft.com/VisualStudio/feedback/details/800104/

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

Initializing a Map with List inside

No, they're not. Consider this:

Map<String, List<String>> keyToGroup = new HashMap<String, ArrayList<String>>();
keyToGroup.put("foo", new LinkedList<String>());

The second line is fine, because a LinkedList<String> is a List<String> - but it's not logically fine in terms of adding it to a HashMap<String, ArrayList<String>>, because a LinkedList<String> is not an ArrayList<String>.

To make it clearer:

Map<String, ArrayList<String>> map1 = new HashMap<String, ArrayList<String>>();
Map<String, List<String>> map2 = map1; // This is invalid
map2.put("foo", new LinkedList<String>());
ArrayList<String> oops = map1.get("foo"); // Because this would be broken

This isn't just the case with collections as the type argument. It's even simpler to see with normal inheritance:

List<Banana> bunchOfBananas = new ArrayList<Banana>();
List<Fruit> fruitBowl = bunchOfBananas; // Invalid!
fruitBowl.add(new Apple());
Banana banana = bunchOfBananas.get(0);

Even though every banana is a fruit, so a "collection of bananas" is a "collection of fruit* in the sense of fetching them, not every fruit is a banana.

You can use wildcard parameterized types to help in some cases, but it depends on exactly what you're trying to achieve.

How can I initialize a std::map with comparison lambda by using an initializer list?

The constructor which takes an initializer list and a comparator is the following:

map( std::initializer_list<value_type> init,
const Compare& comp = Compare(),
const Allocator& alloc = Allocator() );

So you should write:

auto comp = [](int a, int b) { return b < a; };
std::map<int, int, decltype(comp)> m{{{5, 6}, {3, 4}, {1, 2}}, comp};


Related Topics



Leave a reply



Submit