Link to home
Start Free TrialLog in
Avatar of TylerRick
TylerRick

asked on

CMap problems: cannot convert to 'unsigned long'

I've never used CMap before and I'm having trouble getting it to work.

I have
 CMap<CString, CString &, CString, CString &> mapElementNameToSymbol;
. It causes
 error C2440: 'abstract declarator' : cannot convert from 'class CString' to 'unsigned long'
. I tried to track that down. It was in D:\VC++\MFC\INCLUDE\afxtempl.h(128):
 inline UINT AFXAPI HashKey(ARG_KEY key)
 ...
  return ((UINT)(void*)(DWORD)key) >> 4;
Why does it need to convert to unsigned long?

Am I using the right types for my template arguments? I want to map chemical element names (which would be strings) to element symbols (which also would be strings). For example, "oxygen" to "O".
Avatar of rwilson032697
rwilson032697

I think your declaration should be:

 CMap<CString, CString *, CString, CString &> mapElementNameToSymbol;

Cheers,

Raymond.

Avatar of TylerRick

ASKER

When I tried that, it gave me
 
 D:\VC++\MFC\INCLUDE\afxtempl.h(1448) : error C2664: 'SetAt' : cannot convert parameter 1 from 'class CString' to 'class CString *'

 At afxtempl.h(1448), it is

 SetAt(newKey, newValue);

where

 KEY newKey;
 VALUE newValue;

KEY and VALUE are CString so I don't know why it wants to convert to class CString *.

So that doesn't seem to work.
1. rwilson is NOT correct, you can happily declare CMap's as you do.  

2. However you must overload the HashKey function.  The idea of this function is that it should return as unique as possible a UINT for each member of the map (doesn't have to be totally unique - but map will be faster with a better hashing algorithm)

3. To fix your program, add this to a header before it meets any CMaps (stdafx.h is a good place for most MFC project's) :-

UINT AFXAPI HashKey( CString& key ) ;

Then add this to any one of your .cpp files (change the algorithm if you like) :-


UINT AFXAPI HashKey( CString& key )
{
      int ll = key.GetLength() ;

      if ( ll == 0 ) return 0 ;
      UINT ui = key[0] ;

      if ( ll > 1 )
      {
            for ( int ii = 1 ; ii < ll ; ii++ )
            {
                  ui ^= ( key[ii] << ((ii+key[ii-1]) % 24) ) ;
            } // for
      } // if

      return ui ;
}

4.  Then a map such as this will work :-

CMap< CString, CString&, CString, CString&> mapNameToSymbol ;

mapNameToSymbol.SetAt( "O", "Oxygen" ) ;

etc..

CString strKey = "O" ;
CString strValue ;
BOOL bLookup = mapNameToSymbol.Lookup( strKey, strValue ) ;

if ( bLookup )
{
CString strTemp ;
strTemp.Format( "Symbol %s is element %s", (LPCTSTR)strKey, (LPCTSTR)strValue ) ;
AfxMessageBox( strTemp ) ;
}



5. If you prefer this answer to rwilson's then you should reject his, and ask me to post a dummy answer.  If you're still stuck then you should add more comments, explaining what additional information/questions you need/have.
ASKER CERTIFIED SOLUTION
Avatar of Answers2000
Answers2000

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Your answer was very helpful! But I have several questions. Could you explain further?

Most importantly, where should I put these things? I'm confused about the best place for things. I tried putting this

 CMap<CString, CString &, CString, CString &> mapElementNameToSymbol;

and the overloaded HashKey( CString& key ) definition in Chemistry.cpp (the source file for the application). That works. But does it make sense? I thought you were supposed to put variable declarations (mapElementNameToSymbol) in the header file. But when I did that in chemistry.h it had a bunch of errors about it being already declared. Do you know why I can't put it there? Where do you put stuff like that? It has global scope (I think), so I don't want it associated with any of my classes, but I have to choose some file. I thought the application source files made the most sense since I want these things to be global to the entire application.

With it in Chemistry.cpp, I tried using it in a function in ChemistryView.cpp. But it said

 error C2065: 'mapElementNameToSymbol' : undeclared identifier

 So now what do I do? I already have #include "Chemistry.h" in ChemistryView.cpp. But since I defined the variable in Chemistry.cpp and not Chemistry.h, it can't find it.

Where should I put

 mapNameToSymbol.SetAt( "O", "Oxygen" ) ;

(the part that initializes my CMap)? I don't want it in any of the functions that need that information (then I'd have to duplicate it in each one). I want it initialized when the application starts, so would my guess is that the CChemistryApp constructor is the most logical place.

I put the declaration

 UINT AFXAPI HashKey( CString& key ) ;

in stdafx.h like you suggested. But I didn't know that was an acceptable place to put declarations for functions. All I've seen there is inclusions (like #include <afxwin.h>).

I'm wondering, where did you come up with the algorithm? Do MFC's creators expect anyone who uses a CMap with CStrings to be able to think up that? I probably don't have to understand it to use it, do I? I'll just trust you that it does what it needs to.

That's more than I asked in the original question and I apologize for all these new questions. I can increase the points if you'd like...
Hi Answers2000! Are you going to answer my additional questions? If not, I'd be happy to give you the points that you deserve.
I'm waiting for a response...
Sorry I didn't see you had a follow up question...
Basically HashKey prototype must be declared before you declare any variables of the map type.  The reason is that when you declare you CMap it expands the CMap template code.

The CMap template code assumes the presence of the HashKey function for the type you are storing in the template.  MFC includes a default HashKey function which processes ints, etc.  For complex data types (e.g. CString) yes you do need to define the hashing function yourself.  [you can also define HashKey function for standard types, as the standard version is a template itself, and any type specific version you declare will take priority].

The reason is that a good hashing code is one that generates as unique a possible a value for the type of data you are likely to be storing.  MFC's designers can't predict that for complex data types, so they left the decision to you.

If you generate a "better" hashing algorithm (less likely identical values are generated by different strings) then the map will perform better.

As for where you declare your HashKey function, stdafx.h is as good a place as any.  Basically you should stick stuff that rarely changes in your stdafx.h (because this becomes part of the pre-compiled header).  The other reason I suggested it is that it usually included before any classes/code of your own which is likely to declare maps etc.



As for stdafx.h #include'ing stuff this is irrelevant.  The C++ compiler treats the #include'd files just as if they were part of the file that included them.  So if your stdafx.h #include's <afxwin.h> it's just as if you typed in all the code in afxwin.h into your stdafx.h.  No magic there.
Anyway the conclusion is the HashKey funciton prototype needs to go before any declarations of your map types.  So either stick it in stdafx.h are stick in it a separate header, which you #include before any other headers, example:

stick HashKey prototype is HashKey.h
stick HashKey function code in HashKey.cpp

HashKey.cpp also #include's stdafx.h then #include's HashKey.h

All other files, e.g. Chemistry.cpp #include this immediately after stdafx.h, e.g.

#include "stdafx.h"
#include "HashKey.h"
#include "Chemistry.h"

OK my next comment will go thru your specific pts to try and make sure I covered everything

>>         Most importantly, where should I put these things? I'm confused about the best place for things. I tried putting this ...etc.

I think I've explained this in my previous comments.  Just make sure the prototype is seen by the compiler before any maps/classes-containing-maps,  the prototype is :-

         UINT AFXAPI HashKey( CString& key ) ;

>>         Where should I put
>>
>>          mapNameToSymbol.SetAt( "O", "Oxygen" ) ;
>>        (the part that initializes my CMap)? I don't want it in any of the functions that need that information (then I'd have to duplicate it in each one). I want it initialized
>>         when the application starts, so would my guess is that the CChemistryApp constructor is the most logical place.

This is as good a place as any.  I would put it the constructor of the class which contains the map.


>>         I'm wondering, where did you come up with the algorithm?
I just hacked this one up, as it works reasonably well in most cases (similar strings don't produce similar values - which is what a typical list etc. contains) and is quick to calculate.  A good computer science book can explaining hashing theory much better than I can.  A good hash algorithm is :-
1. Easy/Quick to calculate
2. Doesn't produce identical values for the strings you use.  As I don't know what strings you store in your apps, I can't recommend a specific algorithm.


>>Do MFC's creators expect anyone who uses a CMap with CStrings to be able to think up that?
Yes, because they don't know what kind of data-values you might store in a map.  This applies for any complex data type.  Personally I think this is weakness of MFC, but that's the way it.  Read the help on HashKey and you'll see I'm right that they do expect you to do this work yourself.

>> I  probably don't have to understand it to use it, do I? I'll just trust you that it does what it needs to.

OK basically I go thru the string and flip bits based on each character in the string, to generate a 24 bit number.  There is no real magic, but what I did was go from L to R and flip a pattern of 8 bits a time (which bits are flipped depend on the previous character in the string).

The reason I picked this algorithm is that highly similar strings will not produce identical results.  Neither will the same string reversed.  


Oops: - 2nd last para:

         OK basically I go thru the string and flip bits based on each character in the string, to generate a **32** bit number.  There is no real magic, but what I did was go

And the other reasons are

- quick to calculate
- not much code
- uses the 32 bits in the UINT (a lot of simple hash values only use the lowest 8 or 16 bits and so are less "unique" for maps containing a lot of items).

Incidentally I used this algorithm in my program which stores 200,000 items in a map.  This was an optimization (the other main one is to init the hash table - see function in CMap).  I got the speed to insert 200,000+ items into a map down from 10mins to < 3 secs (333 MHz PC)
Thanks for all your help!