C++ compile-time type question

Started by
15 comments, last by Deyja 17 years, 1 month ago
I have a simulation that stores values set while running in the form of a "Profile" template container into a "State" class. At the moment there are 5 types of values that could be set to the State (Matrix, Quaternion, double, int, bool). A Profile is a list< pair<double, T> > (where the double is the time, and the T is the value), and a State is one map for each type in the form of map<string, Profile<XXX> > (where the string is the variable name and the Profile is previously defined). When the user wants to get a value from the State at a later time, right now he must call state.getDoubleAtTime(double time, string varname), or state.getLastInt(varname), or one of many type specific calls (3 different calls for each type). I _ASSERTE'd the heck out of the calls to inform the user when they are asking for a value in the wrong type from the type that the string was set to, but that's just in debug mode. I still have to have a return value for those functions for when they actually call it in release, and those values are all null. However, the user might not know this, and inadvertantly use the wrong values which is not an acceptible solution. I think this falls into the just-in-time compiling vs compile-time type checking, so that in C++ I'm pretty sure I can't specify the return from one of those functions as <T>, and let it find the right value... Is there a way to get around this that doesnt have bad bad consequences?
Advertisement
Quote:Original post by chaospilot
I think this falls into the just-in-time compiling vs compile-time type checking, so that in C++ I'm pretty sure I can't specify the return from one of those functions as <T>, and let it find the right value...


no. it get's into good ol'fashion error checking:

bool readInt( const std::string &input, int &val ){    //yea i know this isn't a real function but you get the idea    if ( input.isNumber() )    {        //blah blah        return true;    }    return false;}


when it returns false you modify your form to display an in-line error message: "that input was not a number".

good GUI programming == lots of if checks to validate the user's data. You also want to be sure that you leave the user-entered values in the form fields so it's easier for them to fix a typo. The only exception would be online website forms where you cannot store the CC number anywhere on your system.

asserts should _never_ be used to verify user input. they should be used to find programming logic problems; users don't obey logic.

asserts crash a program. you never want a released program to crash. therefore, if you hit an assert you have a bug that needs to be fixed before anyone uses your program.

-me
Hummm, I dont think I explained myself clearly enough. The user is going to write simple C++ code to define some functionality of the thing we are simulating. Then he is going to compile his code and throw in some input files and set it running for a few days. (I'm building a simulation framework that will be modified for further use by the customer). If at any time is operating on invalid data (whether we are cout'ing "Invalid data type referenced, returning null values." or not the simulation is a bust.

Is there any way to return a type T, or keep Profile<T>'s of multiple types in the same map and specify the return type during runtime?

Like having the user make a call to the getStateValue(variablename) that returns a type T profile where the type is defined when the variable is looked up?
No i don't think there is but I'm sure you could construct something that would do what you want. Off the top of my head:

struct VoodooVar{    enum Type    {         INT,         FLOAT,         ETC    };    Type type;    union    {        int anInt;        float aFloat;    };};


However, I think that's the wrong approach. You should have code built in that checks the code that the user writes to guarantee that these crashy edge cases will never happen.

If the text input is bad that also can be handled with a pre-process pass over the data to ensure proper formatting and such.

If the data is well constructed and your app well designed their code should never be allowed to generate the errors. If the data is mal-formatted your app should be able to tell and let them know before anything gets executed.

If I'm still not understanding perhaps you could post a specific example of a data-set and the code that the user would write.

-me
The point is to prevent the user from writing incorrect code in the first place. A good starting point would be redesigning your system so that incorrect types cannot be mixed together.

Other than that, you can use the visitor pattern:

class operation{public:  virtual void operator()(const float &) = 0;  virtual void operator()(const int &) = 0;  virtual void operator()(const double &) = 0;  virtual void operator()(const Matrix &) = 0;  virtual void operator()(const Quaternion &) = 0;};


All operations on data must go through one of these operations. So, if he doesn't take into account the possibility of incorrect data, it's his problem and not yours, because you asked for code to handle every possible type of data. Pattern-matched strongly typed languages often use this, for instance.
I think what you are talking about is our last implementation a "StateVar" container, which was saving variables as a container with a type and empty data variables for EACH type. Then when you set it, it would set it's type and the data in that member type to the correct data, and all other data would be set to zeros. But you could still try and read the wrong data from that StateVar without generating a compile time error (just a cout warning). Can you manually generate compile-time errors?

//My simplified example is someone setting data via:
double timeIn= 23.4;
double doubleIn = 9.3490581;
Profile<double> doubleProfIn(make_pair(timeIn, doubleIn));
State newState;
newState.setValue("ImportantDoubleToSave", doubleProfIn);

//Then reading the wrong data via:
int importantValue = newState.getLastInt("ImportantDoubleToSave");

This would generate a debug ASSERT error now(but you said that wasn't a good idea). But in release mode, it would just cout an error message from the state when it checks its map of int profiles and sees no key equal to that. Since we have to return a value, it would return NULL (which is significantly different than 9.3490581).

More explanation.. The state get() check does a key find in the map where the key is the variable name. However, there are multiple maps (one for each Profile template type since you can't mix different template types in one map *I think*). So, when the find by key returns the end iterator, we can't look in the other containers, because we're already committed to returning the type the user is asking for (which could be int when the data is a Matrix). How can we generate compile-time errors when the user is setting a (string,double) to the map and then asking for a Matrix with the correct string?
Quote:Original post by chaospilot
How can we generate compile-time errors when the user is setting a (string,double) to the map and then asking for a Matrix with the correct string?


You can't. Two reasons: first, it depends on potential outside elements, which may only cause problems in certain situations (thus forcing you to discard many situations where nothing bad would have happened). Second, even with outside elements, it can be reduced to the halting problem, so you either have false positives or false negatives.

In short: you let the user lose type information, by associating several types with a single one. You lose the compiler's help. You must now face the issue alone: nothing can ensure that the string-value association you're looking for is correct.

If you want type-safety, keep type information around, which involves using keys other than strings (for instance, a Key<T> which can be initialized from a string and cast back to a string — this would give the compiler the knowledge that the string is associated with a given type of value).
I'm really tired and inattentive at the moment, but it sounds like boost::any and/or boost::variant might be relevant?
I looked at the boost libraries and saw things for conversions between single types, but all the return types were the same. I think the problem is more fundamental. You can't define return types at run-time with key lookups on strings in a number of maps. That said, however, I think if we forced the user to create all variable names through a template Key<T>(string Varname) in the same area and under penalty of death disallowed them from creating Keys on the fly in their code... then unless an idiot declared two keys at the top with identical variable names then it should be fine... that is...

class Subsystem
{
Subsystem();

~Subsystem();

void doSomethingFunction(const State& oldState, State& newState)
{
// USER - INSTANTIATE KEYS HERE
VarNameKey<double> XPositionKey("PositionX");
VarNameKey<Matrix> PositionKeyX("PositionX");
// USER - END KEY INSTANTIATION

pair<double, double> xPos = oldState.getValue(XPositionKey); // GOOD AND CORRECT
pair<double, Matrix> xPos = newState.getValue(XPositionKey); // BAD AND COMPILE TIME FOUND
pair<double, Matrix> xPos = newState.getValue(PositionKeyX); // BAD AND UNFOUND BUT REALLY STUPID (WILL RETURN NULL MATRIX)

double oldTime = xPos.first;
double oldX = xPos.second;

double newTime = oldTime + 2;
double newX = oldX * 2;

Profile<double> doubleProfIn(make_pair(newTime, newX));
newState.setValue(XPositionKey, doubleProfIn);
}
}



Combining ToohrVyk's and Zahlman's suggestions together, I think using boost::variant with the visitor pattern (which is built into boost::variant's functionality) is the most reliable way to do what you want. You don't get to use return types directly like you'd wish, and have to bother making visitor classes, but you definitely get type safety.
"We should have a great fewer disputes in the world if words were taken for what they are, the signs of our ideas only, and not for things themselves." - John Locke

This topic is closed to new replies.

Advertisement