Reputation: 308
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'?
Upvotes: 0
Views: 634
Reputation: 308
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.
Upvotes: 0
Reputation: 438
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.
Upvotes: 1
Reputation: 7863
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
.
Upvotes: 1
Reputation: 12968
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);
Upvotes: 1