Sign in to follow this  
chaospilot

C++ compile-time type question

Recommended Posts

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?

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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?

Share this post


Link to post
Share on other sites
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).

Share this post


Link to post
Share on other sites
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);
}
}



Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
I'm not sure I understand boost::variant. I've read the site, but am still a little shady. So, you're saying with boost::variant you can save different classes to a STL template container and then determine during compile time that you have the wrong type? Because if its just a container class with conversion definitions, then it won't work. The user cannot be allowed to implicity convert data he saved in one form to another form.

Share this post


Link to post
Share on other sites
I havnt read the whole thread but boost::variant is indeed what you need, admitedly the boost documentation for it is a little confusing/vauge imo so heres an example....


typedef boost::variant
<
float,
int,
double,
Matrix,
Quenterion
> value_type;

std::map<std::string, value_type> my_map;

// instead of getLast
template<typename Func>
void operate_on_last(const std::string& var_name, Func function)
{
boost::apply_visitor(function, my_map[var_name]);
}

// user code
struct do_something_with_last_int : boost::static_visitor<>
{
// they expect an int
void operator()(int& i)
{
i *= 2;
}
};

struct do_something_else : boost::static_visitor<>
{
// they dont care what type it is as long as what they want to do can be
// done to that type
template<typename T>
void operator()(T& t)
{
t *= t + 5;
}
};

int main()
{
// user code
// this will only compile if "my var" is an int
operate_on_last("my var", do_something_with_last_int());
// this will compile provided "other var" can have an int added to it
// and can be multipled by something of the sane type
operate_on_last("other var", do_something_else());
}

Share this post


Link to post
Share on other sites
Quote:
Original post by chaospilot
I'm not sure I understand boost::variant. I've read the site, but am still a little shady. So, you're saying with boost::variant you can save different classes to a STL template container and then determine during compile time that you have the wrong type? Because if its just a container class with conversion definitions, then it won't work. The user cannot be allowed to implicity convert data he saved in one form to another form.
After reading over the thread a couple of times I'm still not entirely clear on what you're attempting to do, but perhaps I can at least help clarify a couple of things about boost::variant, and how you might use it in this context.

Here's a short example program I whipped showing basic use of variant:

#include <iostream>
#include <map>
#include <string>

#include <boost/variant.hpp>

using std::cout;
using std::endl;

struct report_type_visitor : boost::static_visitor<> {
void operator()(const bool&) const { cout << "I am of type bool." << endl; }
void operator()(const int&) const { cout << "I am of type int." << endl; }
void operator()(const float&) const { cout << "I am of type float." << endl; }
};

int main()
{
typedef boost::variant< bool, int, float > variant_t;

std::map< std::string, variant_t > map;

map["bool"] = true;
map["int"] = 42;
map["float"] = 3.14f;

cout << std::boolalpha;
cout << map["bool"] << endl;
cout << map["int"] << endl;
cout << map["float"] << endl;
cout << endl;

boost::apply_visitor(report_type_visitor(), map["bool"]);
boost::apply_visitor(report_type_visitor(), map["int"]);
boost::apply_visitor(report_type_visitor(), map["float"]);
cout << endl;

try {
bool b = boost::get<bool>(map["bool"]); // Good...
int i = boost::get<int>(map["float"]); // Bad, run-time error...
} catch (const boost::bad_get& e) {
cout << "The following exception occurred: " << e.what() << endl;
}
}



The key ideas here are that:

1. boost::get<>() provides a straightforward means of retrieving the current value of a variant object. Its behavior is somewhat similar to dynamic_cast (and boost::any_cast) in that on failure it either throws an exception or returns a null pointer (depending on the argument type). Obviously these are run-time errors, but the caller is at least given an opportunity to recover gracefully.

2. boost::apply_visitor is a somewhat less brittle and perhaps more powerful way of operating on a variant's value. If implemented correctly, the visitor will always 'do the right thing' with respect to the variant's currently stored type.

3. variant is 'smart enough' to interface with stream objects correctly based on its currently stored type, so if streaming is all that's required it may not even be necessary to 'request' a value of a particular type (this is demonstrated in the example above).

Whether and how you can use variant here probably depends somewhat on what exactly the client is going to be doing with the data stored in the map. I wasn't quite able to pick that up from reading the thread, so I can't really comment further on that.

Unfortunately I have to run, so I'm not going to be able to double-check this post or clean it up as I would normally :) I can't guarantee that all the information presented in this post is accurate, but I believe the example code is correct - if nothing else, perhaps that will be of some use to you.

[Edit: Foiled by Julian90! Well, at least now the OP has plenty of examples to refer to :) ]

Share this post


Link to post
Share on other sites
First off, many many thanks to those who have posted above to help me out! I now understand boost::variant reasonably well. I'll just scrub the obscurity and post exactly what might need to be done, and why the implicit conversion (or dynamic casting) in boost is probably the wrong way to go... The simulation is a spacecraft utility analysis framework. Each subsystem needs to be an independently compiled module, thus at each call for the subsystem to perform it's job, it must go to the state, get it's previous conditions, it's new commands and simulate the event. Then it must write all of it's data back to the state and prepare to simulate the same subsystem in a different universe (it is an exhaustive search, cropped after a certain period of time). Since the search must progress in time, susbsystems do not calculate sucessive events. They may do event 43 from schedule 3, then do event 50 from schedule 4, then do....

Thus, getting from and setting to the Event->State is very important. A schedule is a list of events BTW. So, our susbsystem is called, passed its initial conditions, and then its commands, and given the new state to fill up the data into. This could involve ode45'ing a diffeq, this could be simple interpolation, etc. However, this could also involve rotation matrices. AFAIK, when using boost, if I asked for a double when the data was stored as a Matrix, it would cast the Matrix as a double(if it didn't give a runtime error). Pretending some aspect of calculation was multiplying together two rotation Matrices to get a third, this would instead call the overloaded * operator in Matrix and do a Matrix*scalar operation instead. Since this is Thesis research for a company who will then want to try and use this for the government, any opportunity for the numbers to be wrong is worse than a run-time error. Compile-time errors are insignificant to the customer. In order of badness from least to most, nothing>compile-time error>run-time error>bad data posing as good.


// Orbital propagator subsystem
class Subsystem
{
Subsystem();

~Subsystem();

bool doEvent(const State& oldState, State& newState, const& Command
newCommand)
{
Matrix satState = oldState.getLastValue("OrbitalPosAndVel").second;

Profile<Matrix> satStateTimeProfile = propagateOrbit(satState);

if(getRadius(min(Profile<Matrix>)) < global.earthradius)
return false;
else
{
newState.set(satStateTimeProfile);
return true;
}
}
}

Share this post


Link to post
Share on other sites
I think your current behaviour is correct: if the user is looking for a key that does not exist for the given type, you should just return an error, as if the key did not exist at all. As this lookup could happen at run-time (for example, the user might enter the variable name during the program execution), there is not much you can do at compile-time. In this case, throwing an exception might be cleaner, actually.

This said, you might improve the program safety at compile time, by replacing your lookup string by a type more complex (a "SafeLookup") that contains the compile-time type. Example:


State
{
void SetValue(SafeLookup<double> &, double);
double GetValue(SafeLookup<double> &) const;

void SetValue(SafeLookup<int> &, int);
int GetValue(SafeLookup<int> &) const;

...
};

SafeLookup<double> MyImportantDouble("MyImportantDouble");

NewState.SetValue(MyImportantDouble, 9.43);

NewState.GetValue(MyImportantDouble);






Depending on the usage of your code, it might help the user not to get mixed up with types.

Share this post


Link to post
Share on other sites
Good reply. That's exactly what I'm doing with one little addendum and I need a smidge of implementation help.

1 issue: There are no run-time queries. All of it is compile-time, with plenty of time to check errors. HOWEVER, we're not talking about hardcore coders using this, otherwise we could count on some basic amount of comfortability with good programming procedure. It's engineers, which means a little silliness might occur.

So the lookup would be something like (with the State int map as map< StateVarKey<int>, Profile<int> >.


template <typename T> Profile<T> State::getProfile(const StateVarKey<T> &key) const;



And in the Profile header file we have:


template <typename T> struct StateVarKey {
string varName; };

const bool operator==(const StateVarKey<T> &A, const StatVarKey<T> &B) const {
return (A.varName == B.varName); }



But I'm having a problem with the equality operator, which I believe since I need to do a map lookup, where the key is a StateVarKey<T>.

Share this post


Link to post
Share on other sites
Two ways to write this == operator.


template <typename T>
struct StateVarKey {
string varName;

//as a member
bool operator==(const StateVarKey& rhs) const
{
return this->varName == rhs.varName;
}
};

//as a non-member
template <typename T>
bool operator==(const StateVarKey<T>& lhs, const StateVarKey<T>& rhs)
{
return lhs.varName == rhs.varName;
}

//if comparing StateVarKey's with different T's is logical
template <typename A, typename B>
bool operator==(const StateVarKey<A>& lhs, const StateVarKey<B>& rhs)
{
return lhs.varName == rhs.varName;
}


Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this