How to overload operator== for static array?

Pages: 12
I have this code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SimVariables {

public:
        static const CString Names[];
	static const CString Units[]; 
   
};


 const CString SimVariables::Names[] = {
"ANGLE OF ATTACK INDICATOR",
			"GUN AMMO",
			"CANNON AMMO",
};


I need to use std::find

1
2
3
4
5
6
7
CString aSub0="CANNON AMMO";

std::array<CString,3>::iterator vit = std::find(SimVariables::Names.begin(), SimVariables::Names.end(), aSub0);
				   if (vit == SimVariables::Names.end()) {
					   cout << "SimVar not found " << endl;
					   aSub0 = "";
				   }


I think I need to write an operator == overload but it needs to be able to deal with a static member. And I need the overload for comparing Names to Names and Units to Units.

Also should I use Cstring or const char*?

I would rather use System::String^ which is how the values are loaded from a file but I don't think I could use std::find with a System::String^.


Thanks,
Chris
Last edited on
neither. use <string> which is standard c++. char array / char* is C and works but its 1990s era code. CString is a horrible microsoft invention that is a poor quality wrapper for char array, on par with a homework assignment to make your own string class. To be fair, it predates <string> so they felt it was useful way back when.

what exactly do you want to compare? <string> has == built into it. CString probably does too, or I thought it did. Maybe that was one of the reasons I hated it... I know it is missing critical things, but can't recall what.

use npos for find failures:
if(vit == std::npos) I think, or very nearly ... look it up if you need to see it (sorry, I dont do this much). npos is usually -1 but you can't rely on it.

I do not know what a string xor is :) (yes, I know, but I stubbornly refuse to allow M$ to screw up another language, I deny their abominations).

I don't think static matters here. but I could be wrong, let us know which string you do end up choosing and we can look again from that angle.

I mean, this works, but I am not sure you need it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct fu
{
  static string s; 	
  bool operator ==(string& z) {return z == s;} 
};
 string fu::s = "moo";


int main()
{
   fu f;
   string l{"words"};
   string m{"moo"};
   if(f == l) cout << "really?";
   if(f == m) cout << "really!";	
}


its a lot of handwaving to say:
if(fu::s == m) //one point/beauty of static class members is you can just access them...
this may be incomplete ... most people would expect if you have this == you would also have fu == other fu
Last edited on
Is there a problem with just using a set?
1
2
3
4
5
6
7
8
9
std::unordered_set<std::string> Names = {
    "ANGLE OF ATTACK INDICATOR",
    "GUN AMMO",
    "CANNON AMMO",
};

auto it = Names.find(aSub0);
if (it == Names.end())
    //not found 


use npos for find failures
Huh??? OP's comparison is correct, you're supposed to compare the return value with the end iterator of the range. Why would you compare to std::npos? Do the most common standard iterator types even have an operator==(size_t) overload?
std::string::find() returns std::string::npos if not found. std::find() returns end()
hah, yes, sorry... had strings on my mind, but those are not really strings, are they... even if they were is array of them. Oops :)
Last edited on
Unordered set of string works!

Thanks,
Chris
Last edited on
Ran into a little snag. I needed to pass string.c_str() to the constructor. However I was getting garbage instead of the string. Then I read that I was getting a pointer with c_str() and as it was going out of scope, the memory it is pointing to becomes invalid. I used strdup() and it seems to be working fine. Any consequences to this? Don't want a memory leak.
Thanks,
Chris
Show the code that was causing an error. I'm not getting a good picture of what's going on. You're mixing std::strings, MFC/ATL CStrings, and C++\CLI System::Strings? Oh my... that sounds like a mess.

If you need a CString, you this will copy it, and the memory will be cleaned up automatically:
1
2
std::string stdstr("foo");
CString cstr(stdstr.c_str());


strdup allocates memory that must be freed. I don't think you'll need to use it if you use a proper alternative.

To convert a std::string to a CLR string, see:
https://docs.microsoft.com/en-us/cpp/dotnet/how-to-convert-standard-string-to-system-string?view=msvc-160
Last edited on
I am trying to build a Windows Forms Application. Here is the section of the code with the issue.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class SimConnectRequest {
public:
    int id=0;
    int q_id = 0; //queue_index
    int def_ID = DEFINITION_PACKAGE_1;
    bool active = false;
    const char* SimVar="";
    const char* SimUnit="";
...

SimConnectRequest(const char* SVar, const char* SUnit, int vtype) :SimVar(SVar), SimUnit(SUnit), var_type(vtype) {
        id = idcounter;        
        idcounter++;
    };
...


I need to pass a const char* to the constructor for SimVar and SimUnit.

Here is where the constructor is called:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
private: System::Void button4_Click(System::Object^ sender, System::EventArgs^ e) {
	String^ fileContent = gcnew String("");
	String^ filePath = gcnew String("");

	openFileDialog1->InitialDirectory = "c:\\";

	openFileDialog1->Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
	openFileDialog1->FilterIndex = 2;
	openFileDialog1->RestoreDirectory = true;

	if (openFileDialog1->ShowDialog() == System::Windows::Forms::DialogResult::OK)
	{
		//Get the path of specified file
		filePath = openFileDialog1->FileName;

		cli::array<String^>^ aLines = System::IO::File::ReadAllLines(filePath);
		
		for (uint32_t i = 0; i < aLines->Length; ++i) {
			cli::array<String^>^ aSubStrings = aLines[i]->Split(',');
			cout << "Length " << aSubStrings->Length << endl;
			if (aSubStrings->Length == 3) {
                              
				string first, second;
				MarshalString(aSubStrings[0], first);
				MarshalString(aSubStrings[1], second);
				int third = Convert::ToInt16(aSubStrings[2]);
				cout << "Variable -> " << first << " = " << second << endl;

				auto it = Names.find(first);
				if (it == Names.end()) {
					first.clear();
				}

				it = Units.find(second);

				if (it == Units.end()) {
					second.clear();
				}

				if ((!first.empty()) && (!second.empty())) {
					//cout << "Good " << first.c_str() << endl;
					SimRec.push_back(SimConnectRequest(strdup(first.c_str()), strdup(second.c_str()), third));
					
					
				}
				else
				{

					// Initializes the variables to pass to the MessageBox.Show method.
					String^ message = "Wrong SimVar or Unit: Line ?" + i;
					String^ caption = "Error in SimVar file";
					MessageBoxButtons buttons = MessageBoxButtons(); //OK only

					// Displays the MessageBox.
					MessageBox::Show(message, caption, buttons);

				}
			}
			else if (aSubStrings->Length <3)  { //FIXME
					// Initializes the variables to pass to the MessageBox.Show method.
					String^ message = "Too few items in a line: " + i;
					String^ caption = "Error in SimVar file";
					MessageBoxButtons buttons = MessageBoxButtons(); //OK only

					// Displays the MessageBox.
					MessageBox::Show(message, caption, buttons);
			}
		}
		load_SimRecs();
		
	}

}


Basically I am trying to convert C# to C++. I don't know how to use Array.Find in c++ so I figured I would convert System::String to string so I could use std::find. It works but when I create the SimmConnectRequest, I am not getting any valid SimVar or SimUnit unless a strdup. I am open to better solutions.

Thanks,
Chris
As a great man once said: It's time to stop!

You're using four different kinds of strings in your code: C strings, std::strings, CStrings, and CLR System::Strings. Pick one and use it everywhere.
you may have to pick two (to use the windows tools, or at least be casting from one to the other a bit), but I agree, you need to stop, talk to us, and design a bit and redo a bit before you proceed with this mess.

first, c++ provides (more than but let us start with) 2 ways to do array finds: find is linear:
https://www.cplusplus.com/reference/algorithm/find/

and the faster if the data is sorted binary search:
https://www.cplusplus.com/reference/algorithm/binary_search/

you may want to start over with the original design and use arrays and avoid trying to rehack it via strings at all. C++ is nothing like C# at first look, but C# IS a red-headed stepchild of C++ and MOST of the syntax is close enough to get the gist and MOST of the things C# can do can be done in C++ with minimal pain and MOST of the things in c++ can be done in C# with minimal pain as long as you are not trying to do very low level tasks (eg embed assembly language or write a hardware interface).

--------------
I need to pass a const char* to the constructor for SimVar and SimUnit.
did you create these things? Can you make it something other than const char* ?
if you cannot make it anything else:
std::string_cstr() is a const char *.
there are other ways too, but that should work for sure.
ALL the string wrapper objects can be turned into a C style char array/pointer. The question is why you need to do so. Most of the time it can be done with just a cast operation or a method.
Last edited on
Oh my Gosh! I know its a mess.

However, if I use string in the SimmConnectRequest class I get:

1
2
Severity	Code	Description	Project	File	Line	Suppression State
Warning	C6262	Function uses '41800' bytes of stack:  exceeds /analyze:stacksize '16384'.. This allocation was for a compiler-generated temporary for 'class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > [1045]' at line 5.  Consider moving some data to heap.	CppCLR_WinformsProjekt3	F:\WindowsProjects\CppCLR_WinformsP3-V1 - 7\CppCLR_WinformsProjekt3\local\SimUtils.h	5	


Don't really know what that means. So I stuck with const char*. And I can't use System String in SimConnectRequest either.

 
A member of a non managed class cannot be a handle. 

Also above my head.

But I do need two types of strings. I need System::String because Windows Forms uses that by default. But I need to bridge the gap to SimConnectRequest class which I can control its design. Similar thing happened with Serialport.write(). It needed a byte array. Found out it was a cli::array, not an std::array which needed to used as an argument.

Can you suggest a suitable conversion between System::String and const char* ( or whatever type string you guys think is most appropriate.

BTW though, I am still curious if the use of strdup in the code causes a memory leak.

This is what I am using to convert in one direction. It sufficed in all other areas because I only needed to display SimVar and SimUnit in a text box. The only time a System::String needs to be converted to const char* is with the above code which builds the objects from an array of System::String derived from a file.
1
2

String^ clistr = gcnew String(SimRec[i].SimVar);


The code snippet above with the multiple strings is the only place in the code so far that required a conversion to string and that's only because I needed std::find. I suppose I could just see if std::find will work with const char*.

Thanks
Chris
Last edited on
That first message is only a warning; you're using 41800 bytes of stack because you're declaring 1045 strings in an array. So apparently your sizeof(string) is 40. It's suggest you allocate the strings on the heap, e.g. you could use a vector<string> instead. But I don't see why you are declaring 1045 strings in the first place. Did you mean to declare one string of length 1045? Because that's very different.

The closest equivalent of std::find for CLR Strings appears to be IndexOf:
https://docs.microsoft.com/en-us/dotnet/api/system.string?view=net-5.0
1
2
3
4
5
6
7
8
9
10
void FindInString(String^ s, String^ substring, StringComparison options)
{
   int result = s->IndexOf(substring, options);
   if (result != -1)
      Console::WriteLine("'{0}' found in {1} at position {2}", 
                        substring, s, result);
   else
      Console::WriteLine("'{0}' not found in {1}", 
                        substring, s);                                                  
}


But yes, you can create a std::string from a const char*,
1
2
std::string str(my_const_char_ptr);
str.find(...);


Yes, the use of strdup will cause a memory leak if you don't free() the data allocated by it. I don't see the need for it here.

1
2
    const char* SimVar="";
    const char* SimUnit="";

If you want to avoid strdup, change these into std::strings.
1
2
std::string SimVar;
std::string SimUnit;

(Or, have them be String^s. I guess it doesn't matter as long the code doesn't blow up)
Last edited on
Yes, the use of strdup will cause a memory leak if you don't free() the data allocated by it. I don't see the need for it here.


Is it because it is a temporary used in a constructor?

Chris

But I don't see why you are declaring 1045 strings in the first place. Did you mean to declare one string of length 1045? Because that's very different.


The number is correct. I am trying to compare files loaded by user from a config file with strings that are recognized by the Simconnect SDK and inform user if the string is not valid. There are about 1400 lines total between SimVars and SimUnits. I guess strings are by default allocated 40 bytes?

This only happens at the beginning of the program. Once the config file is loaded, I don't need to keep the strings. I was thinking of a way that I might conserve memory which is why I thought maybe a Class with a static string list which I don't intend to create an object. But I ran into the issue on the top post.
I created an ordered set but I would like to dispose of that after I validated that the configuration file has correct entries and they have been loaded properly into SimConnectRequest objects. Perhaps a vector would be better which I could zero out after use? I really don't know enough of the memory side of things and am open to an alternative solution.

Chris
Last edited on
The Findstring function you provided opens up an idea.

I could load the entire SimVar and SimUnit list from a file into one big String^ each using Filereader. It think that's what it does. I still go line by line with the user created configuration. I will be able to look for a user substring in that long String of SimVar and SimUnit to validate if the user String is a good string. Then after the function exits, the big String from FileReader goes out of scope. Wouldn't need std::find. And there is only one conversion to const char that needed to be done.

I can't use String^ in SimConnectRequest class. If I could,I wouldn't need to do any conversion at all.

Chris
what can you use there?
Why can't you use System::String there?
I did some more reading and found out I can create a class in Visual Studio using Add Class.
It built a SimConnectVars class like so:

1
2
3
4
5
6
7
8
9
ref class SimConnectVars  //->got ref
{
public:
    ...
    
	System::String^ SimVar;
	System::String^ SimUnit;
        int req_ID = REQUEST_PACKAGE_1;


Anyway, I removed the old SimConnectRequest and Replaced with the new SimConnectVars. I changed the vector to:

1
2
//Now declared inside public ref class Form1
private: cliext::vector< SimConnectVars^> SimRecs;


I changed all occurences of SimRec.<member> to SimRecs-><member>.
Changed the push_back to the vector to :

1
2
//SimRecs.push_back(SimConnectRequest(strdup(first.c_str()), strdup(second.c_str()), third));
SimRecs.push_back(gcnew SimConnectVars(aSubStrings[0], aSubStrings[1], third));  //Notice it is now using System::String^ so I can delete all the string conversions once I have a System::String std::find or similar.   


And braced for a huge segfault and surprisingly it ran.

However, I did run into some snags. So I commented out some parts. I am not sure how to convert these from the old SimConnectRequest struct

OLD: SimConnectRequest
1
2
3
4
5
6
7
8
9
//static member of the SimConnectRequest class
    static double dvalue[100]; 
    static uint16_t cvalue[50];

//Initialization outside class
double SimConnectRequest::dvalue[] = {};
uint16_t SimConnectRequest::cvalue[] = {};



Also it seems in the NEW:SimmConnectVars, I can do this in the class:

1
2
    static int idcounter=0;
    static int buffer_counter=0;


In the NEW:SimConnectVars, my guess for the dvalue and cvalue would be:
1
2
    static cli::array<double>^ dvalue = gcnew cli::array<double>(100);
    static cli::array<uint16_t>^ cvalue = gcnew cli::array<uint16_t>(100);


It does compile, and I did not put an outside class initializer.

For the OLD:SimConnectRequest methods that dealt with dvalue and cvalue:

1
2
3
4
5
6
7
8
9
10
11
void set_data_buffer(int d_index) {
        index = &dvalue[d_index];
        cindex = &cvalue[d_index];
        q_id = d_index;
        buffer_counter = d_index+1;
    }
    double getdouble() {
        if (index != NULL)
            return *(double*)this->index;
        else return 0;
    }


I replaced with :

1
2
3
4
5
6
7
8
9
10
11
void set_data_buffer(int d_index) {
        pin_ptr<double> index =  &dvalue[d_index]; //I am afraid this might be a local to function variable and wont affect the class member with the same name. 
        pin_ptr<uint16_t> cindex = &cvalue[d_index];
        q_id = d_index;
        buffer_counter = d_index + 1;
    }
    double getdouble() {   //this is the same as before but I put it here in case you think it needs changed
        if (index != NULL)
            return *(double*)this->index;
        else return 0;
    }


The only piece of code I don't know how to convert is :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
switch (pObjData->dwRequestID) {
           case REQUEST_PACKAGE_1: {
        //GET DATA IN DOUBLE type
               double* x = (double*)&pObjData->dwData;
               memcpy(SimConnectVars::dvalue, x, (int)pObjData->dwDefineCount * 8);  //HOW TO CONVERT? It says "Argument of type cli::array is incompatible with void*

              


               //CONVERT DOUBLE TO UINT16_T
               for (int i = 0; i < SimConnectVars::buffer_counter; i++) {   
                   SimConnectVars::cvalue[i]= Encode(*(SimConnectVars::dvalue + i));
                  //HOW TO CONVERT? It says "No operator "+" matches these operands, operands are cli::array<double,1 >^ and int"
               }
               
               

               break;
           }


So I haven't really tested the part of the program that actually reads data from a Simconnect SDK data request, into a array of double. I also haven't tested the part that converts the double into a uint16_t.


Any hints?

Thanks,
Chris
Last edited on
This is my attempt at the last code conversion but I am not getting any data.

1
2
3
4
5
6
7
8
9
10
11
12
13
case REQUEST_PACKAGE_1: {
               cout << "REQUEST IN " << endl;
               double* x = (double*)&pObjData->dwData;
               pin_ptr<double> pinPtrDArray = &SimConnectVars::dvalue[0];
               memcpy_s(pinPtrDArray, (int)pObjData->dwDefineCount * 8, x, (int)pObjData->dwDefineCount * 8); // multiple to size of double

               //Convert to uint16_t values and store in cvalue
               pin_ptr<uint16_t> pinPtrCArray = &SimConnectVars::cvalue[0];
               for (int i = 0; i < SimConnectVars::buffer_counter; i++) {   
                   SimConnectVars::cvalue[i]= Encode(*(pinPtrCArray + i));
                  
               }
               


Thanks,
Chris
Pages: 12