C++ STL map not recognizing key - c++

I have this code, CBString is just a string class I use for some processing
char * scrummyconfigure::dosub(strtype input)
{
CBString tstring;
tstring = input;
uint begin;
uint end;
begin = tstring.findchr('$');
end = tstring.findchr('}',begin);
CBString k = tstring.midstr(begin+2,end-2); // this is BASE
strtype vname = (strtype) ((const unsigned char*)k);
strtype bvar = (strtype) "BASE";
assert(strcmp(bvar,vname) == 0); // this never fails
// theconf is just a struct with the map subvars
// subvars is a map<const char *, const char *>
out(theconf->subvars[bvar]); // always comes up with the value
out(theconf->subvars[vname]); // always empty
uint size = end - begin;
tstring.remove(begin, size);
return (const char *)tstring; // it's OKAY! it's got an overload that does things correctly
//inline operator const char* () const { return (const char *)data; } <-- this is how it is declared in the header of the library
}
Why is it that the strcmp always says the strings are the same, but only the variable I declared as bvar returns anything?

I'm assuming strtype is defined in the following way:
typedef char * strtype
Your issue is that you're assuming that vname and bvar have the same value, where in reality, they have different values that each point to a block of memory that contains identical data.
std::map is dumbly comparing them with ==, and I bet you'd find that if you compared them with ==, you would get false, as expected. Why exactly arent you using the std::string class?
Edit: I rewrote your method to be less scary:
// implied using namespace std;
string ScrummyConfigure::removeVariableOrSomething(string tstring)
{
uint begin; // I'll assume uint is a typedef to unsigned int
uint end;
begin = tstring.find('$', 0);
end = tstring.find('}', begin);
string vname = tstring.substr(begin + 2, end - 2); // this is supposedly BASE
assert(vname == "BASE"); // this should be true if vname actually is BASE
out(this->conf->subvars[bvar]); // wherever theconf used to be, its now a member
out(this->conf->subvars[vname]); // of ScrummyConfigure and its been renamed to conf
uint size = end - begin;
tstring.erase(begin, size);
return tstring; // no casting necessary
}

//subvars is a map<const char *, const char *>
The key of this map isn't a string per-say, but a memory address. The corresponding check would be
assert( bvar == vname);
which will probably fail. You'll need to change the key type to a string class (either std::string or CBString to meaningfully use the map.

Just because the strings are the same doesn't mean that std::map will treat them as the same key. That depends on the Compare class that is used by the std::map, which defaults to less<KeyType> - which yields the same result as applying the less-than operator.
You can define a class that defines operator() to do a proper comparison on your strtypes and pass that as your third template argument when defining your std::map. Or, as suggested, use std::string as your strtype.

Related

std::set find behavior with char * type

I have below code line:
const char *values[] = { "I", "We", "You", "We"};
std::set<const char*> setValues;
for( int i = 0; i < 3; i++ ) {
const char *val = values[i];
std::set<const char*>::iterator it = setValues.find( val );
if( it == setValues.end() ) {
setValues.insert( val );
}
else {
cout << "Existing value" << endl;
}
}
With this I am trying to insert non-repeated values in a set, but somehow code is not hitting to print for existing element and duplicate value is getting inserted.
What is wrong here?
The std::set<T>::find uses a default operator < of the type T.
Your type is const char*. This is a pointer to an address in memory so the find method just compares address in memory of given string to addresses in memory of all strings from set. These addresses are different for each string (unless compiler optimizes it out).
You need to tell std::set how to compare strings correctly. I can see that AnatolyS already wrote how to do it in his answer.
You should define less predicate for const char* and pass into the set template to make the set object works correctly with pointers:
struct cstrless {
bool operator()(const char* a, const char* b) const {
return strcmp(a, b) < 0;
}
};
std::set<const char*, cstrless> setValues;
Unless you use a custom comparison function object, std::set uses operator<(const key_type&,key_type&) by default. Two pointers are equal if, and only if they point to the same object.
Here is an example of three objects:
char a[] = "apple";
char b[] = "apple";
const char (&c)[6] = "apple"
First two are arrays, the third is an lvalue reference that is bound to a string literal object that is also an array. Being separate objects, their address is of course also different. So, if you were to write:
setValues.insert(a)
bool is_in_map = setValues.find("apple") != setValues.end();
The value of is_in_map would be false, because the set contains only the address of the string in a, and not the address of the string in the literal - even though the content of the strings are same.
Solution: Don't use operator< to compare pointers to c strings. Use std::strcmp instead. With std::set, this means using a custom comparison object. However, you aren't done with caveats yet. You must still make sure that the strings stay in memory as long as they are pointed to by the keys in the set. For example, this would be a mistake:
char a[] = "apple";
setValues.insert(a);
return setValues; // oops, we returned setValues outside of the scope
// but it contains a pointer to the string that
// is no longer valid outside of this scope
Solution: Take care of scope, or just use std::string.
(This answer plagiarises my own answer about std::map here)

Map C-style string to int using C++ STL?

Mapping of string to int is working fine.
std::map<std::string, int> // working
But I want to map C-style string to int
For example:
char A[10] = "apple";
map<char*,int> mapp;
mapp[A] = 10;
But when I try to access the value mapped to "apple" I am getting a garbage value instead of 10. Why it doesn't behave the same as std::string?
map<char*,int> mapp;
They key type here is not "c string". At least not, if we define c string to be "an array of characters, with null terminator". The key type, which is char*, is a pointer to a character object. The distinction is important. You aren't storing strings in the map. You are storing pointers, and the strings live elsewhere.
Unless you use a custom comparison function object, std::map uses operator<(const key_type&,key_type&) by default. Two pointers are equal if, and only if they point to the same object.
Here is an example of three objects:
char A[] = "apple";
char B[] = "apple";
const char (&C)[6] = "apple"
First two are arrays, the third is an lvalue reference that is bound to a string literal object that is also an array. Being separate objects, their address is of course also different. So, if you were to write:
mapp[A] = 10;
std::cout << mapp[B];
std::cout << mapp[C];
The output would be 0 for each, because you hadn't initialized mapp[B] nor mapp[C], so they will be value initialized by operator[]. The key values are different, even though each array contains the same characters.
Solution: Don't use operator< to compare pointers to c strings. Use std::strcmp instead. With std::map, this means using a custom comparison object. However, you aren't done with caveats yet. You must still make sure that the strings must stay in memory as long as they are pointed to by the keys in the map. For example, this would be a mistake:
char A[] = "apple";
mapp[A] = 10;
return mapp; // oops, we returned mapp outside of the scope
// but it contains a pointer to the string that
// is no longer valid outside of this scope
Solution: Take care of scope, or just use std::string.
It can be done but you need a smarter version of string:
struct CString {
CString(const char *str) {
strcpy(string, str);
}
CString(const CString &copy); // Copy constructor will be needed.
char string[50]; // Or char * if you want to go that way, but you will need
// to be careful about memory so you can already see hardships ahead.
bool operator<(const CString &rhs) {
return strcmp(string, rhs.string) < 0;
}
}
map<CString,int> mapp;
mapp["someString"] = 5;
But as you can likely see, this is a huge hassle. There are probably some things that i have missed or overlooked as well.
You could also use a comparison function:
struct cmpStr{
bool operator()(const char *a, const char *b) const {
return strcmp(a, b) < 0;
}
};
map<char *,int> mapp;
char A[5] = "A";
mapp[A] = 5;
But there is a lot of external memory management, what happens if As memory goes but the map remains, UB. This is still a nightmare.
Just use a std::string.

C++ Appending to an array

const int fileLength = fileContent.length();
char test[1000];
for (int p = 0; p < fileLength; ++p){
test[p].append(fileContent[p]); // Error: expression must have class type
};
I'm attempting to append the characters of a text file into an array which i've created. Though I'm getting the error " expression must have class type ". Tried googling this error to no avail.
test is an array of char. test[p] is a char. char does not have any members. In particular, it does not have an append member.
You probably want to make test a std::vector<char>
const auto fileLength = fileContent.length();
std::vector<char> test;
for (const auto ch : fileContent)
{
test.push_back(ch);
}
or even:
std::vector<char> test( fileContent.begin(), fileContent.end() );
if you then really need to treat the test as an array (because you are interfacing to some C function for example), then use:
char* test_pointer = &*test.begin();
If you want to use it as a nul-terminated string, then you should probably use std::string instead, and get the pointer with test.c_str().
char array does not have any member function by the name append .However , std::string does have one member function named append like below :
string& append (const char* s, size_t n);
I think you by mistake have used char array instead of std::string .
std::string will resolve this problem like below :
const int fileLength = fileContent.length();
string test;
for (int p = 0; p < fileLength; ++p){
test.append(fileContent[p],1); // Error: expression must have class type
};
Better way would be string test(fileContent) .You can access test just like a array .Refer string class for more details .

How to force std::map::find() to search by value

From what I have deduced, the std::map::find() method searches the map by comparising pointer address instead of values. Example:
std::string aa = "asd";
const char* a = aa.c_str();
const char* b = "asd";
// m_options is a std::map<const char*, int )
m_options.insert( std::make_pair( a, 0 ) );
if( m_options.find( b ) != m_options.end() ) {
// won't reach this place
}
I am kinda surprised (because I am using primitive types instead of some class) and I think that I have done something wrong, if not then how to force it to use value instead of address?
You are using char * as a key type for the map. For the pointer types, comparison is performed by their address (as the map cannot know that these pointers are NULL-terminated 8-bit strings).
To achieve your goal, you could create the map with custom compare function, e.g.:
bool MyStringCompare(const char *s1, const char *s2) {
return strcmp(s1, s2) < 0;
}
...
std::map<const char*, int, MyStringCompare> m_options;
Or consider using std::string as the key type.
Actually, map uses a strict ordering comparison operator to look for values, not the equality operator. Anyway, you can achieve this by passing a custom functor that compares the values of the strings, or do the right thing and use std::string instead.

Inserting objects into hash table (C++)

This is my first time making a hash table. I'm trying to associate strings (the keys) with pointers to objects (the data) of class Strain.
// Simulation.h
#include <ext/hash_map>
using namespace __gnu_cxx;
struct eqstr
{
bool operator()(const char * s1, const char * s2) const
{
return strcmp(s1, s2) == 0;
}
};
...
hash_map< const char *, Strain *, hash< const char * >, struct eqstr > liveStrainTable;
In the Simulation.cpp file, I attempt to initialize the table:
string MRCA;
for ( int b = 0; b < SEQ_LENGTH; b++ ) {
int randBase = rgen.uniform(0,NUM_BASES);
MRCA.push_back( BASES[ randBase ] );
}
Strain * firstStrainPtr;
firstStrainPtr = new Strain( idCtr, MRCA, NUM_STEPS );
liveStrainTable[ MRCA ]= firstStrainPtr;
I get an error message that reads "no match for ‘operator[]’ in ‘((Simulation*)this)->Simulation::liveStrainTable[MRCA]’." I've also tried using "liveStrainTable.insert(...)" in different ways, to no avail.
Would really love some help on this. I'm having a difficult time understanding the syntax appropriate for SGI hash_map, and the SGI reference barely clarifies anything for me. Thanks.
Try liveStrainTable[ MRCA.c_str() ]= firstStrainPtr;. It expects const char * as type of key value, but MRCA has type string.
Another way is to change liveStrainTable to:
hash_map< string, Strain *, hash<string>, eqstr > liveStrainTable;
Others answered your direct question, but may I suggest using unordered_map instead - it's coming with the next version of the STL and is supported by all major compilers.
hash_map is not part of STL. There's no implementation provided for hash, or in other words, the hash_map can't hash strings by default. You need your own hash function. T
Try:
typedef struct {
size_t operator()( const string& str ) const {
return __gnu_cxx::__stl_hash_string( str.c_str() );
}
} strhash;
hash_map< string, Strain *, strhash, eqstr > liveStrainTable;
The hash_map is defined with const char * as the key type and you are using an std::string as the key when accessing. These are 2 different types, the template did not build an operator for the second type, so this is an error. Use std::string for the hashmap definition or use MRCA.c_str()
Right now, you have a type mis-match. You're passing MRCA (a string) where a char const * is expected. You can either use c_str() to get a char const * from the string, or (far better) change the definition of your hash table to take a string as its key type.