How to create a std::map of constant values which is still accessible by the [] operator?
Solution 1
Couldn't you use the insert()
method available for std::map
?
http://www.cplusplus.com/reference/map/map/insert/
Edit : (solution) myMap.insert(std::pair<std::string, const int>("Keys", 42));
As far as I understand it, the reason why this works is because the constructor for the pair, pair (const first_type& a, const second_type& b)
, initializes its members first
and second
using the constructors for first_type
and second_type
, taking a
and b
as their respective parameter.
With the solution you were trying to use, my comprehension is that myMap["Keys"] = 42;
initializes the member second
of the map
(of type const int
) using the default constructor for int
. Then a value is attempted to be assigned to that member. As this is done outside the constructor of the class map
, the const
declaration makes this impossible.
With the solution using insert()
, the members are initialized in the constructor of the pair
. Thus they can be declared const
. The same operation is done when the pair
is copied to the map
.
Solution 2
While this is not possible for you, others who wants to do this and that have a C++11 compatible compiler, could use uniform initialization:
std::map<std::string, const int> myMap = {
{ "keys", 42 }
};
Oh and by the way, don't define the map in the header file. Instead declare it as extern
in the header file, then define it in the source file.
Solution 3
The simplest solution is to write your own, wrapping the standard map class:
template <typename KeyType, typename MappedType, typename CmpType>
class ConstantMap
{
typedef std::map<KeyType, MappedType, CmpType> Impl;
Impl myImpl;
public:
typedef Impl::value_type value_type;
template <ForwardIterator>
ConstantMap( ForwardIterator begin, ForwardIterator end, CmpType cmp = CmpType() )
: myImpl( begin, end, cmp )
{
}
// necessary if [] is not going to work for missing keys
bool contains( KeyType const& key ) const
{
return myImpl.find( key ) != myImpl.end();
}
MappedType const& operator[]( KeyType const& key ) const
{
Impl::const_iterator elem = myImpl.find( key );
if ( elem == myImpl.end() ) {
// Not found, do what you want (maybe throw an exception)
}
return elem.second;
}
};
You can initialze the map by passing it iterators to a sequence
of anything which can convert to value_type
.
Depending on your needs, you may want to add additional forwarding typedefs, functions, etc. If you're using C++11, you may also want to create a constructor which can use a list initializer.
Solution 4
If the map
can't mutate, you should use const map<string, int>
, and not map<string, const int>
: the second version allows insertion and deletion of objects.
Sadly, you are going to have to lose the []
operator; C++ doesn't have an ImmutableMap
, or something like that. However, std::map::at
and std::map::find
aren't too bad...
Comments
-
Chaos_99 almost 2 years
I need a std:map data structure that is read only, which means I have to fill it once with data and then only read those values, never change them or add additional ones.
My non-const version looks like this:
//in .h #include <string> #include <map> std::map<std::string, int> myMap; void initMap(); //in .cpp #include "foo.h" void initMap() { myMap["Keys"] = 42; }
Then I'd call
initMap()
once in my code and be done.Now I've read several questions here already and it seems non-trivial to achieve const-ness for the map.
Making it a
std::map<std::string, const int>
won't allow me to fill it in theinitMap()
. Filling it with a non-const temp and the copy constructor on definition doesn't work either, as the copy constructor doesn't easily take the non-const version as input.Making it a
const std::map<std::string, int>
(which I could fill with a non-const copy during definition) would disable the use of the[]
operator for value access.So is there a way to achieve (value) const-ness and initialize the structure (preferably in the header file)?
BTW: Neither C++0x nor C++11 nor
boost::
is an option. -
Chaos_99 almost 11 yearsIf it's between the [] operator and insertion/deletion, I'd go with insertion/deletion. Not using something by convention is easier to 'sell' to other devs then clunky access syntax.
-
Chaos_99 almost 11 yearsI've edited the original question. No C++0x also means no C++11. But you're absolutely right about the declare/define. Thanks!
-
Chaos_99 almost 11 yearsExpand your answer to be way more verbatim and actually answer my question and I accept is as right. Indeed, using my own code example above, replacing
myMap["Keys"] = 42;
bymyMap.insert(std::make_pair("Keys", 42))
will work for astd::map<std::string, const int>
. But I have no idea why this one works and the other one not. -
Chaos_99 almost 11 yearsAnd I just verified, it correctly gives me a compiler error on write access (
myMap["Keys"] = 23;
) while still allowing read access (std::cout << myMap["Keys"]
). -
Chaos_99 almost 11 yearsThanks for the wrapper class example code. I'm sure it may come handy to a lot of people searching for a custom map class. But I'll take @Matzar s solution as long as const-ness is my only need.
-
Matzar almost 11 years@Chaos_99 Sorry. I would've preferred to write it as a comment, but I'm not yet allowed to.
-
Matzar almost 11 years@Chaos_99 I added more in-depth explanations to my answer. This applies to the solution that I gave as an example. If you use
myMap.insert(std::make_pair("Keys", 42))
, the principle is the same, but the initialization of theconst int
is only done in the constructor ofmap
, since the type of the members of thepair
will be whatever it decides is most fitting for"Keys"
and42
. -
Chaos_99 almost 7 yearsMight work for this simple example, but running a switch statement with thousands of options is probably not faster than a std::map access.