How can I convert between enum and cstring without map? - c++

I've seen this answered before but using map or vectors, but I can't use outside libraries for my project, so I need to figure out another way. For converting to a cstring I'm using a function with a switch case and it works, but for converting from a cstring to the enum isn't going as planned.
The method I came up with for converting a cstring to an enum is to first cast the enum to an int (none, first, second, etc.. becomes 0, 1, 2, etc...) so that I can use a for loop to iterate through the different enums. Next, using the enum to cstring function, I compare the string passed in to the string given by the converting function. If they are equal, the enum is set. This seems like a pretty convoluted way to do this, and unsurprisingly, I can't get it working.
Here's all my test code, the setType function is where things go wrong.
enum type { none, first, second, third, fourth };
const char* typeName(type name);
type setType(char* name); // trouble here
int myStrComp(const char *str1, const char *str2); // compare cstrings
int main() { // test the function
char testName[] = "second";
type testType = setType(testName);
std::cout << typeName(testType) << std::endl; // should print "second"
}
const char* typeName(type name) { // convert enum to cstring
switch (name) {
case none: return '\0'; break;
case first: return "first"; break;
case second: return "second"; break;
case third: return "third"; break;
case fourth: return "fourth"; break;
}
}
type setType(char* name) {
type temp;
for (int i = 0; i < 4; i++) { // I know, inefficient
temp = static_cast<type>(i); // but there's only 5 to check
if (myStrComp(name, typeName(temp)) == 0) {
return temp;
}
}
return none; // shouldn't get here
}
int myStrComp(const char *str1, const char *str2) {
while (*str1 == *str2) {
if (!*str1) {
return 0; // strings are equal
}
str1++;
str2++;
}
return *str1 - *str2; // how different are they alphabetically
}

case none: return '\0'; break;
This has single quotes, so it returns character \0, which as an integer is equal to 0. When converted to pointer, this is a null pointer. When you try to dereference a null pointer in myStrComp(), an access violation happens.
Instead, you can use return ""; to return an empty string.
Possible way to simplify typeName is to use an array:
const char* typeName[] = {"", "first", "second", "third", "fourth"};
if (myStrComp(name, typeName[i]) == 0)
(This will cause access violation if i is out of bounds.)

To answer your question, you can associate enums with text by using a lookup table:
struct Entry
{
type enum_type;
const char * enum_text;
};
Entry enum_conversion_table[] =
{
{none, "none"},
{first, "first"},
{second, "second"},
{third, "third"},
{fourth, "fourth"},
};
static const size_t conversion_table_capacity =
sizeof(conversion_table) / sizeof(conversion_table[0]);
Converting from enum to text:
Search the table for an entry with the key enum.
Return a pointer to the text field of the entry, if found, or nullptr if not found.
Converting from text to enum:
Search the table for an entry with the key enum text.
Return the enum value if found, or create another enum value for "unknown" and return that.
This technique:
1. Doesn't use any libraries.
2. The data can be placed into the constant data section and stored into read-only memory.
3. The code can access the data directly.
4. The data is initialized before main().

Related

Parse Parameter-Value-string and link value (of type string) to variable (of specific type)

How would you structure a set of parameter value pairs if the parameter data types are different?
Suppose you have a large set of parameters that are bound in a larger software like this:
string name;
int counter;
//...
float temperature;
bool enableState;
To set the value of a parameter manually a string should be accepted via commandline (e.g. "Name=Eric","Counter=100","Temperature=20.0",...).
The search is for a suitable structure for the parameters so that they (or at least the information to read/write them) can be stored in an array. Since the values have different data types, they cannot be stored in an array, but is it purposeful with their pointers (?).
How can the (string) values of the input string be assigned to variables that have fix data types?
Whatever I'm trying doesn't lead to anything or it is a tremendous effort, which makes me believe that a possible (and simple) solution might not be trivial.
The code is supposed to run on an arduino.
Edit:
I found a way to do it and it seems to work quite well. But I'm not sure, maybe I did fundamental misstakes. Could you confirm that the code in my answer is legal, so I can set the question to 'solved'?
You could use a std::map<std::string, std::function<void(std::string&)>> variableMap and fill it up with lambdas like this.
std::string name;
int counter;
...
variableMap["Name"] = [&](std::string& val) {
name = val;
}
variableMap["Counter"] = [&](std::string& val) {
counter = std::stoi(val);
}
And then use it something like this
//Get the input and parse it, eg "Name=Chris"
std::string variableName; // Holds "Name"
std::string variableValue; // Holds "Chris"
if (variableMap.find(variableName) != variableMap.end())
variableMap[variableName](variableValue);
Take a look at std::variant or std::any (both available in C++17). If you're using an earlier standard, boost provides boost::variant and boost::any.
You may be able to have a struct store the data as a unsigned char * and then have some meta data about how to decode the unsigned char * in the struct. Something like
typedef struct FlexibleStruct {
char * metaData;
// this data type here doesn't matter because we will cast it to what we want
unsigned char * data;
} FlexibleStruct;
Then when you want to store it:
FlexibleStruct f;
f.metaData = "float";
f.data = (unsigned char *)floatToStore; // This should be a pointer to a float on the heap
and to retrieve the data:
if(strcmp(f.metaData, "float") == 0) {
float *retrievedFloat = (float *)f.data;
printf("%f\n", *retrievedFloat);
}
This will work with any data type (even structs) in any version of c or c++!
I tested this on gcc 6.4.0, but I don't know if it will work for avr-g++. I can provide more information if you'd like.
I found a way to do it, but I'm not sure if I will get problems with memory allocation. Beside others I also need string parameters linked to global string variables. The parameters need to be global as well. I'm linking the variables to the parameters with thier memory address, but I'm not sure if that's legal...
My solution is:
#include <iostream>
#include <string>
enum Access {NO_ACCESS,READ,WRITE,READ_WRITE};
enum DataType {INTEGER,FLOAT,STRING,BOOLEAN};
// parameter-class
class Parameter {
public:
long int address;
string (*setEventAddress) ();
string (*getEventAddress) ();
string parameterName;
Access access;
DataType datatype;
string set(string value) {
if(!(access==WRITE || access==READ_WRITE)){
return "ERROR: no write access";
}
string response="";
switch (datatype){
case INTEGER:
response = setInteger(value,address);
break;
case FLOAT:
response = setFloat(value,address);
break;
case STRING:
response = setString(value,address);
break;
case BOOLEAN:
response = setBool(value,address);
break;
default:
response = "ERROR: unknown data type";
break;
}
if(setEventAddress!=NULL){
string eventResponse=setEventAddress();
}
return response;
}
string get(){
if(!(access==READ || access==READ_WRITE)){
return "ERROR: no read access";
}
string response="";
switch (datatype){
case INTEGER:
response = getInteger(address);
break;
case FLOAT:
response = getFloat(address);
break;
case STRING:
response = getString(address);
break;
case BOOLEAN:
response = getBool(address);
break;
default:
response = "ERROR: unknown data type";
break;
}
if(setEventAddress!=NULL){
string eventResponse=setEventAddress();
}
return response;
}
string setInteger(string valueRaw, long int address) {
int value = stoi(valueRaw);
*(int*)(address)=value;
return "Done";
}
string setFloat(string valueRaw, long int address) {
float value = stof(valueRaw);
*(float*)(address)=value;
return "Done";
}
string setString(string valueRaw, long int address) {
string value = valueRaw;
*(string*)(address)=value;
return "Done";
}
string setBool(string valueRaw, long int address) {
bool value = true;
if (valueRaw.compare("true") == 0) {
*(bool*)(address)=true;
} else {
*(bool*)(address)=false;
}
return "Done";
}
string getInteger(long int address) {
int& i = *reinterpret_cast<int*>(address);
return to_string(i);
}
string getFloat(long int address) {
float& f = *reinterpret_cast<float*>(address);
return to_string(f);
}
string getString(long int address) {
string& s = *reinterpret_cast<string*>(address);
return s;
}
string getBool(long int address) {
bool& b = *reinterpret_cast<bool*>(address);
return to_string(b);
}
Parameter(string n, Access a, DataType d, long int varPointer, string (*setFuncPointer)(), string (*getFuncPointer)());
};
// constructor for parameter
Parameter::Parameter(string n, Access a, DataType d, long int varPointer, string (*setFuncPointer)()=NULL, string (*getFuncPointer)()=NULL) {
parameterName=n;
access=a;
datatype=d;
address=varPointer;
setEventAddress=setFuncPointer;
getEventAddress=getFuncPointer;
}
// functions optionally executed when parameter becomes set/get
string exampleSetEvent() {
// do anything when parameter is set
return m;
}
string exampleGetEvent() {
// do anything when parameter is read
return m;
}
// a parameter constists of the variable and an object of the parameter-class
// and is simply introduced by adding only two lines
string name;
Parameter Name ("Name", READ_WRITE, STRING, (long int) &name);
int counter;
Parameter Counter ("Counter",READ_WRITE, STRING, (long int) &counter, exampleSetEvent, example);
float temperature;
Parameter Temperature ("Temperature", READ_WRITE, STRING, (long int) &temperature);
bool enableState;
Parameter EnableState ("EnableState", READ_WRITE, STRING, (long int) &enableState);
Does anyone see any problems?
In my understanding the memory address of a global object necessarily must stay the same during the entire runtime. Is that correct?
How about strings? As the size of strings is not given explicitly, how can the memory for a string be allocated at compile time? Only the start address of a string gets allocated, I guess.

How to make efficient C++ jump table?

I'm beginner to C++ and I have implemented the following simple jump table, but was wondering if I'm doing it the right way. Is there anyway I can improve the following code?
The following code is using a dictionary (I'm from a C# background) to store functions' pointers.
#include <cstdio>
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
void Zero() { printf("Zero\n"); }
void One() { printf("One\n"); }
void Two() { printf("Two\n"); }
void Three() { printf("Three\n"); }
string prompt()
{
printf("Enter number from 0 to 3 or q to quit:\n");
string line;
getline(cin, line);
return line;
}
int main(int argc, const char * argv[]) {
unordered_map<string, void(*)()> map;
map["0"] = Zero;
map["1"] = One;
map["2"] = Two;
map["3"] = Three;
while (true) {
string c = prompt();
if (c == "q") break;
map[c]();
}
return 0;
}
How about a switch statement?
switch (c) {
case 0:
printf("Zero\n"); break;
case 1:
printf("One\n"); break;
case 2:
printf("Two\n"); break;
case 3:
printf("Three\n"); break;
default:
break;
}
There's not much you can do to make your code "faster" without going for the switch solution which breaks the original idea of having an array of functions. If you only gonna use 'characters' such as '0' => '9', 'a' => 'z' you could dodge the memory allocation needed for the string, and you could also initialize your map with an initializer_list, and you could also make such array const static if that's viable.
Here goes my "optimized" code if it helps.
inline char prompt() //this function will probably 900% be inlined even if you don't specify the inlike keyword
{
printf("Enter number from 0 to 3 or q to quit:\n");
char v;
while (!(std::cin >> v)); //Just to make sure we get valid input
return v;
}
int main()
{
static const std::unordered_map<char, void(*)()> mymap =
{
{ '0' , Zero },
{ '1' , One },
{ '2' , Two },
{ '3' , Three }
};
while(1)
{
auto it = mymap.find(prompt());
// Without this check, your program will crash if input is invalid.
if (it != mymap.end())
{
it->second();
break;
}
}
return 0;
}
Pleas provide more details for your case of efficiency. Do you mean memory/cpu cycles/pass-through?
According to your code:
it's not error prone (use auto it = map.find(key); function for searching and check output it != map.end() value, so no new elements will be created)
it's good enough for string key type
you case easily make more flexible by replacing function pointer with std::function<void()>
It terms of more low-level control you can you custom hash function and custom hash tables implementations.
On some data it may be usefull to consider std::map or sorted std::vector as an option.
As static lookup is fast, This will perform very good irrespective of compiler. Jump table differs from compiler to compiler. I would use following code, May be some people will object to this as global are bad. But before commenting, please evaluate this
string prompt()
{
printf("Enter number from 0 to 3 or q to quit:\n");
string line;
getline(cin, line);
return line;
}
enum Choice = {ZERO = 0, ONE, TWO, THREE};
static char *choice_str[] = {
"Zero",
"One",
"Two",
"Three"
};
int main(int argc, const char * argv[]) {
while (true) {
string c = prompt();
if (c == "q")
{
break;
}
else {
assert(atoi(c) >= Choice::ZERO && atoi(c) <=Choice::THREE);
printf("%s\n", choice_str[atoi(c)]);
}
}

Find string inside 2D char array in C

I am trying to find a string which is inside 2D char array and return it's index. For example:
char idTable[255][32];
char tester[] = { 't','e','s','t','e','r','\0' };
memcpy(idTable[43], tester, 7);
uint8_t id = getID(name[0]);
//name is returned from function "char **name = func();"
//but I have the same results when I try using normal char array...
I've had partial success with the first part of the below code, but it is finding a match if a part of the word is the same (one, oneTwo). If I add "else if" to the first "if" it always goes to the "else if".
The rest of the file prints different results for
printf("idTable string lenght:\t %u\n", strlen(idTable[index]));
and
printf("foundMatch string lenght:\t %u\n", strlen(foundMatch));
, unless I add printf("Index:\t %i\n", index);.
uint8_t getID(char *name) {
printf("\nInserted name:\t %s\n", name);
uint8_t index;
for (uint8_t r = 0; r < 255; r++) {
if (strstr(idTable[r], name) != NULL) {
printf("Found '%s' in position:\t %d\n", name, r);
index = r;
}
}
printf("Index:\t %i\n", index); // THIS LINE
char foundMatch[strlen(idTable[index])];
printf("idTable string lenght:\t %u\n", strlen(idTable[index]));
for (uint8_t c=0; c<strlen(idTable[index]); c++) {
foundMatch[c] = idTable[index][c];
}
printf("foundMatch string lenght:\t %u\n", strlen(foundMatch));
if (strcmp(foundMatch, nodeName) == 0) {
printf("Confirmed\n");
return index;
} else {
printf("Second test failed\n");
return 0;
}
}
Why am I getting this strange results and is there a better way to do this?
I don't know how you are initializing your idTable entries, but if you are using the method that you showed at the start of the question you'll have problems. You can't assume all of the space reserved by idTable is initialed to 0's, so idTable[43] isn't a null terminated string. Therefore idTable[43] need not compare equal to the null terminated string "tester".
Also your getID function doesn't return anything despite its signature. So it won't even compile as-is.
Here's a solution in actual C++, not C.
std::array<std::string, 255> idTable;
idTable.at(43) = "tester";
std::pair<std::size_t, std::size_t> findInIdTable(std::string const& what) {
for (unsigned i = 0; i < idTable.size(); ++i) {
std::size_t pos = idTable.at(i).find(what);
if (pos != std::string::npos) {
return std::make_pair(i, pos);
}
}
// if the code reaches this place, it means "not found". Choose how you want to deal with it
// my personal suggestion would be to return std::optional<std::pair<...> instead.
}
If you want to discard the pos value, it's easy to change as well.
Live On Coliru
In the category: Use C++
Of course, use std::array<char, 32> or std::string if possible. I stuck with your choices for this answer:
Live On Coliru
#include <algorithm>
#include <iostream>
#include <cstring>
char idTable[255][32] = { };
int main() {
using namespace std;
// initialize an entry
copy_n("tester", 7, idTable[43]);
// find match
auto match = [](const char* a) { return strcmp(a, "tester") == 0; };
auto index = find_if(begin(idTable), end(idTable), match) - idTable;
// print result
cout << "match at: " << index;
}
Prints
match at: 43
You need to add a nul to the end of the foundMatch array after copying in the idTable row:
foundMatch[strlen(idTable[index])] = '\0';
right before the 'foundMatch string lenght' (length) message.
strlen is an expensive function that walks the string every time. You should call that once, store it in a local variable, then reference that variable rather than calling strlen repeatedly.

How do I use a string name instead of a variable?

Suppose I have the following func and want to compare field as variable and value as value. How can I do it?
bool ReadFile::compareField(string field, string value)
{
if (field == "value")
}
If you're talking about C++, then the answer is: you can't. Variables are a compile-time thing; they don't exist at run-time.
If you want to access parameters as strings, then you might consider using e.g. a std::map:
class MyClass
{
private:
std::map<std::string, int> params;
public:
MyClass()
{
params["height"] = 165;
params["weight"] = 65;
params["legs"] = 2;
}
int getParam(const std::string &str) const
{
return params[str];
}
};
I changed the above func without map to the following func:
bool ReadFile::compareField( string * field, string value){
int i;
string fieldName = *field;
//converting to lower case
for (i = 0; i< strlen(value.c_str());i++)
value[i] = tolower(value[i]);
for (i = 0; i< strlen(fieldName.c_str());i++)
fieldName[i] = tolower(fieldName[i]);
/////
cout << fieldName << endl;
if (strstr(fieldName.c_str(),value.c_str()) != NULL){
return true;
}
return false;
}
At first i convert to lowercase and then search with strstr func, But here is a nice note that i use address of that variable instead of its name.

C++ map object not growing when members added

Below the map 'widgets' is always size of 1 for some reason. There should be 4 when it's done.
Output:
Widget: widget_ram_label:layout_bar:0 1
Widget: widget_ram_active:layout_bar:0 1
Widget: widget_ram_total:layout_bar:0 1
Widget: widget_wlan0_label:layout_bar:0 1
Here's widgets:
std::map<const char *, Widget *> widgets;
And here's the code:
void Generic::BuildLayouts() {
for(std::map<const char*, std::vector<widget_template> >::iterator l =
widget_templates.begin(); l != widget_templates.end(); l++) {
std::vector<widget_template> layout = l->second;
for(unsigned int i = 0; i < layout.size(); i++ ) {
Json::Value *widget_v = CFG_Fetch_Raw(root, layout[i].key);
if(!widget_v) {
error("No widget named <%s>", layout[i].key);
continue;
}
Json::Value *type = CFG_Fetch_Raw(widget_v, "type");
if(!type) {
error("Widget <%s> has no type!", layout[i].key);
delete widget_v;
continue;
}
Widget *widget;
char name[256];
sprintf(name, "%s:%s", layout[i].key, l->first);
char tmp[256];
int i = 0;
sprintf(tmp, "%s:%d", name, i);
while(widgets.find(tmp) != widgets.end()) {
i++;
sprintf(tmp, "%s:%d", name, i);
}
memcpy(name, tmp, 256);
if(strcmp(type->asCString(), "text") == 0) {
widget = (Widget *) new WidgetText(this, name, widget_v,
layout[i].row, layout[i].col);
std::cout << "Widget: " << name << " " << widgets.size() << std::endl;
} else {
error("Unknown widget type: %s", type->asCString());
}
if(widget) {
widgets[name] = widget;
}
delete type;
}
}
}
std::map<const char *, Widget *> widgets;
Don't do this. Never use char* as a map key. (The map uses the std comparison std::less for its keys and that compares the addresses for pointers.)
Do yourself a favour and use std::string. Once you mastered this, you might try to go back dealing with C strings again.
Maybe because all your name pointers poitns to the same buffer? Because the content of name changes, but not the value of the pointer to name in the map.
Try to use std::string instead.
Replace you name buffer by
#include <string >
//...
std::string name = "the name";
and replace your map by
std::map< const std::string , Widget* > widgets;
That will make your life easier, safer and more readable.
To help formatting, use std::stringstream or boost string algorithms.
A start of example, this code :
char name[256];
sprintf(name, "%s:%s", layout[i].key, l->first);
char tmp[256];
int i = 0;
sprintf(tmp, "%s:%d", name, i);
while(widgets.find(tmp) != widgets.end()) {
i++;
sprintf(tmp, "%s:%d", name, i);
}
memcpy(name, tmp, 256);
would be written like this:
Widget *widget = NULL; // always initialize your variables!!!
std::stringstream name_stream; // this will let us format our string
name_stream << layout[i].key << ":" << l->first;
std::string name = name_stream.str(); // now we got the string formated.
std::stringstream tmp_stream; // same for tmp
tmp_stream << name << ":" << i; // will automatically convert basic types, see the doc if you want specific formatting
std::string tmp = tmp_stream.str(); // now we got the string formated.
// the while loop have no sense : a map have only one value by key
// if you want to have several values by key, use std::multi_map instead -- it don't work exactly the same though
// for now i'll just assume you just need to find the value associated to the name:
typedef std::map< const std::string, Widget* > WidgetMap; // for ease of writing, make a shortcut! ease your life!
WidgetMap::iterator tmp_it = widgets.find( tmp );
if( tmp_it != widgets.end() )
{
// starting here I don't understand you code, so I'll let you find the rest :)
}
Your map doesn't know how to compare C-style strings properly. If you insist on using C-style strings as keys for your map, you need to supply the proper comparator. For example, it could be done as follows
inline bool str_less(const char *s1, const char *s2) {
return strcmp(s1, s2) < 0;
}
...
std::map<const char *, Widget *, bool (*)(const char *, const char *)> widgets(str_less);
Or, if you wish, you can implement the comparator as a class-based functor.
But that's not all. Your map does not know how to (and will not) manage memory for your const char * keys. You are passing a pointer to a local short-lived buffer to the map as a key. This is totally useless. In case of const char * keys you are supposed to manage the key memory by yourself. For example, you can allocate each key dynamically and pass a pointer to such a dynamically allocated and properly initialized key buffer to the map. And later, when the time comes to destroy your map, it is your responsibility to deallocate all these key buffers manually.
This all is a major pain in the neck. For this reason you should be much better off using 'std::string' as the key type, not a C-style string.
As an alternative to using std::string, you can just strdup the buffer before using it. Just remember to free it when you free your std::map.
It's probably hashing on the address of name. Use std:string.