Creating and populating a map in a header file in C++
Asked Answered
A

5

8

It doesn't seem like C++ really has a way to do this, but I'm hoping I'm wrong.

What I'd like to do is create a map (in this case to map one set of strings to another, not that the datatypes should matter really) inside a header file, so that multiple source files can then access that map directly.

Right now I just have a function defined in my header file and implemented in the source file that essentially does this exact function, but I'd prefer to use a map since I would then be able to see at one glance what the keys/values are.

The problem I'm running into with this is that populating the map requires executable code, which I can't exactly just place into a header file. Is there any good way to achieve what I'm trying to do?

Alitaalitha answered 6/5, 2014 at 20:54 Comment(3)
extern is your friend.Protozoon
Can you please expand on that?Alitaalitha
Global variables are your enemy.Bartholomeus
P
3

Here is a solution which does not use C++11. This example also uses three files just to 'prove' it works.

If the map is not intended to be changed, then the initialization should be moved from foo.cpp to foo.h and made const.

foo.h

// FOO.H STARTS HERE
#include <map>
#include <string>
#include <vector>

typedef std::pair<std::string, std::string> stringpair_t;
const stringpair_t map_start_values[] = {
  stringpair_t("Cat", "Feline"),
  stringpair_t("Dog", "Canine"),
  stringpair_t("Fish", "Fish")
};

const int map_start_values_size = sizeof(map_start_values) / sizeof(map_start_values[0]);

extern std::map <std::string, std::string> my_map;

// FOO.H ENDS HERE

foo.cpp

// FOO.CPP STARTS HERE
#include <iostream>

#include "foo.h"

std::map<std::string, std::string> my_map (map_start_values, map_start_values + map_start_values_size);

int main2(void);

int main (void) {
  for (std::map<std::string, std::string>::const_iterator i = my_map.begin(); i != my_map.end(); ++i) {
    std::cout << "A " << i->first << " is a " << i->second << std::endl;
  }

  main2();

  return 0;
}

// FOO.CPP ENDS HERE

foo2.cpp

// FOO2.CPP STARTS HERE
#include "foo.h"

#include <iostream>

int main2 (void) {
  for (std::map<std::string, std::string>::const_iterator i = my_map.begin(); i != my_map.end(); ++i) {
    std::cout << "A " << i->first << " is a " << i->second << std::endl;
  }

  return 0;
}

// FOO2.CPP ENDS HERE
Pulsar answered 6/5, 2014 at 21:51 Comment(3)
Yup, this one works in my environment. I just realized that I can't use the [] operator to access the elements tho, since the map is const, I have to use .at() or the iterators. This is probably a stupid question, but is there any way for [] to work with a const map? It just seems like a more elegant way of accessing the map.Alitaalitha
I've been coding in python lately so I know exactly where you're coming from here. C++ maps are really just not good with this kind of handwavium. The answer is no. std::map::operator[](int) has to modify the array. std::map::at() is actually a C++11 method, but if it works for you, then great because IMO it's exactly what you're looking for here and there's no reason to shy away from it. I'm not sure why you want to shy away from calling a named method.Pulsar
It's just one more thing to remember, not a really big deal, but one step further away from the super-clean interface I wanted. I think this solution will work well though, now all of the pairs can be in the .h file and there's no need to scan through a c++ function to determine what the pairs are.Alitaalitha
P
14

There's a couple of ways to do this.

You can declare the variable in a header file, but you need to define it in a cpp file...

// FOO.H STARTS HERE
#include <map>
#include <string>

extern std::map<std::string, std::string> my_map;
// FOO.H ENDS 

// FOO.CPP STARTS HERE
#include <iostream>

#include "foo.h"

std::map<std::string, std::string> my_map = {
  { "Cat", "Feline" },
  { "Dog", "Canine" },
  { "Fish", "Fish" }
};

int main (void) {
  for (auto i = my_map.begin(); i != my_map.end(); ++i) {
    std::cout << "A " << i->first << " is a " << i->second << std::endl;
  }

  return 0;
}

// FOO.CPP ENDS HERE

Alternatively, you can just keep the map in a header as long as it's const. This has the downside that it obviously only works if you don't need to change the map.

// FOO.H STARTS HERE
#include <map>
#include <string>

const std::map<std::string, std::string> my_map = {
  { "Cat", "Feline" },
  { "Dog", "Canine" },
  { "Fish", "Fish" }
};

// FOO.H ENDS HERE

// FOO.CPP STARTS HERE
#include <iostream>

#include "foo.h"

int main (void) {
  for (auto i = my_map.begin(); i != my_map.end(); ++i) {
    std::cout << "A " << i->first << " is a " << i->second << std::endl;
  }

  return 0;
}

// FOO.CPP ENDS HERE

Both of these examples assume C++11, for the use of the nice and pretty map = { } initializer.

Pulsar answered 6/5, 2014 at 21:30 Comment(3)
This is pretty much the answer I was looking for, too bad I can't use C++11 in my environment yet :(Alitaalitha
Check my other answer. This can be done without C++11, but it's a bit uglier.Pulsar
thank you for explaining how to do with extern. this is a much more generalizable answer variation!Phlox
P
3

You can use an initializer list in C++ 11: http://en.cppreference.com/w/cpp/language/list_initialization

But this doesn't solve the problem. You don't want to initialize it in a header file, you will most likely get linker errors (multiple symbol definition).

You can: 1. Use the extern keyword and initialize it in a cpp file as global variable. 2. You can make it a static variable inside a class (but still initialize it in a cpp file).

Pennyroyal answered 6/5, 2014 at 21:3 Comment(1)
Unfortunately my company doesn't support C++11 yet and it doesn't look like it will happen anytime soon. I guess I'll just stick with my if-else statement for now. I might make a class with the map as its only member, and populate it in the constructor, but it's certainly not an elegant solution.Alitaalitha
P
3

Here is a solution which does not use C++11. This example also uses three files just to 'prove' it works.

If the map is not intended to be changed, then the initialization should be moved from foo.cpp to foo.h and made const.

foo.h

// FOO.H STARTS HERE
#include <map>
#include <string>
#include <vector>

typedef std::pair<std::string, std::string> stringpair_t;
const stringpair_t map_start_values[] = {
  stringpair_t("Cat", "Feline"),
  stringpair_t("Dog", "Canine"),
  stringpair_t("Fish", "Fish")
};

const int map_start_values_size = sizeof(map_start_values) / sizeof(map_start_values[0]);

extern std::map <std::string, std::string> my_map;

// FOO.H ENDS HERE

foo.cpp

// FOO.CPP STARTS HERE
#include <iostream>

#include "foo.h"

std::map<std::string, std::string> my_map (map_start_values, map_start_values + map_start_values_size);

int main2(void);

int main (void) {
  for (std::map<std::string, std::string>::const_iterator i = my_map.begin(); i != my_map.end(); ++i) {
    std::cout << "A " << i->first << " is a " << i->second << std::endl;
  }

  main2();

  return 0;
}

// FOO.CPP ENDS HERE

foo2.cpp

// FOO2.CPP STARTS HERE
#include "foo.h"

#include <iostream>

int main2 (void) {
  for (std::map<std::string, std::string>::const_iterator i = my_map.begin(); i != my_map.end(); ++i) {
    std::cout << "A " << i->first << " is a " << i->second << std::endl;
  }

  return 0;
}

// FOO2.CPP ENDS HERE
Pulsar answered 6/5, 2014 at 21:51 Comment(3)
Yup, this one works in my environment. I just realized that I can't use the [] operator to access the elements tho, since the map is const, I have to use .at() or the iterators. This is probably a stupid question, but is there any way for [] to work with a const map? It just seems like a more elegant way of accessing the map.Alitaalitha
I've been coding in python lately so I know exactly where you're coming from here. C++ maps are really just not good with this kind of handwavium. The answer is no. std::map::operator[](int) has to modify the array. std::map::at() is actually a C++11 method, but if it works for you, then great because IMO it's exactly what you're looking for here and there's no reason to shy away from it. I'm not sure why you want to shy away from calling a named method.Pulsar
It's just one more thing to remember, not a really big deal, but one step further away from the super-clean interface I wanted. I think this solution will work well though, now all of the pairs can be in the .h file and there's no need to scan through a c++ function to determine what the pairs are.Alitaalitha
E
3

I took this as a challenge -- is there a header-only solution using C++03 syntax?

The answer is "yes":

// GlobalMap.hpp
#include <map>

typedef std::map<std::string, std::string> GMap;

inline
GMap & globalMap()
{
    static GMap theMap;
    static bool firstTime = true;
    if(firstTime)
    {
        firstTime = false;
        theMap["Cat"] = "Feline";
        theMap["Dog"] = "Canine";
        theMap["Guppy"] = "Fish";
    }
    return theMap;
}

To test this:

#include <iostream>
#include "GlobalMap.hpp"
int main()
{
    for(
        GMap::const_iterator it = globalMap().begin();
        it != globalMap().end();
        ++it)
    {
        std::cout << '[' << it->first << "]=" << it->second << std::endl;
    }
   return 0;
}

Produces this output:

[Cat]=Feline
[Dog]=Canine
[Guppy]=Fish

Note I'm not recommending this solution. Just showing it as an interesting possibility.

Entourage answered 6/5, 2014 at 22:9 Comment(0)
B
0

I had a situation where the map could not be const, but it made way more sense in terms of readability to define the map values in the header. To deal with this situation you can #define your map literal in the header as a macro, and then put the macro in the .cpp file. It's not always ideal to use #defines, but in my case it was worth it for readability.

In the .h file:

#define MY_MAP_LITERAL td::map<std::string, std::string> my_map = {\
  { "Cat", "Feline" },\
  { "Dog", "Canine" },\
  { "Fish", "Fish" }\
};

In the .cpp file:

MY_MAP_LITERAL
Bechtold answered 5/3, 2022 at 0:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.