Template casting issue
I seem to be getting an error in the below code when I attempt to cast to a template of class T, when T is of type float. I have realized already that a type of int functions correctly, because the following is valid syntax:
char* str = "3";
int num = (int)str;
The same is not true of float. I'm wondering if there is a way to stop the g++ compiler erroring on a type mismatch so I can handle it with the RTTI method typeid().
class LuaConfig {
// Rest of code omitted...
// template currently supports both string and int
template <class T> T getC(const char *key) {
lua_pushstring(luaState, key);
lua_gettable(luaState, -2);
if (!lua_isnumber(luaState, -1)) {
// throw error
std::开发者_开发问答cout << "NOT A NUMBER" << std::endl;
}
T res;
// WHERE THE PROBLEM IS:
if ( typeid(T) == typeid(int)
|| typeid(T) == typeid(float)
) {
std::cout << "AS NUM" << std::endl;
// Floats should fall in here, but never does because of the
// else clause failing at compile time.
res = (T)lua_tonumber(luaState, -1);
} else {
// TODO: Fails on float here, it should fall down the
// first branch (above). This branch should only be for string data.
std::cout << "AS STRING" << std::endl;
res = (T)lua_tostring(luaState, -1); // LINE THAT CAUSES ISSUE.
}
std::cout << "OUT:" << res << std::endl;
lua_pop(luaState, 1);
return res;
}
}
int main( int argc, char* args[] ) {
LuaConfig *conf = new LuaConfig();
std::cout << conf->getC<int>("width") << std::endl;
std::cout << conf->getC<float>("width") << std::endl; // This causes the error.
}
The error g++ throws is:
source/Main.cpp:128: error: invalid cast from type ‘char*’ to type ‘float’
Try to avoid C-style casts. If you write (int)ptr where ptr is some pointer this will be a reinterpret_cast which is probably not what you want. For converting numbers to strings and back again check various FAQs. One way to do this is to use the std::stringstream class.
A C-style cast is dangerous because it can be used for lots of things and it's not always apparent what it does. C++ offers alternatives (static_cast, dynamic_cast, const_cast, reinterpret_cast) and a functional-style cast which is equivalent to a static cast).
In the case of (int)ptr it converts the pointer to an int and not the string representation of a number the pointer points to.
You might also want to check out Boost's lexical_cast.
Edit: Don't use typeid for this. You can handle this completely at compile-time:
template<typename T> struct doit; // no definition
template<> struct doit<int> {
static void foo() {
// action 1 for ints
}
};
template<> struct doit<float> {
static void foo() {
// action 2 for floats
}
};
....
template<typename T> void blah(T x) {
// common stuff
doit<T>::foo(); // specific stuff
// common stuff
}
In case T is neither int nor float you get a compile-time error. I hope you get the idea.
You need branching at compile time. Change the content in your template to something like this:
template<typename T> struct id { };
// template currently supports both string and int
template <class T> T getC(const char *key) {
lua_pushstring(luaState, key);
lua_gettable(luaState, -2);
if (!lua_isnumber(luaState, -1)) {
// throw error
std::cout << "NOT A NUMBER" << std::endl;
}
T res = getCConvert(luaState, -1, id<T>())
std::cout << "OUT:" << res << std::endl;
lua_pop(luaState, 1);
return res;
}
// make the general version convert to string
template<typename T>
T getCConvert(LuaState s, int i, id<T>) {
return (T)lua_tostring(s, i);
}
// special versions for numbers
float getCConvert(LuaState s, int i, id<int>) {
return (float)lua_tonumber(s, i);
}
int getCConvert(LuaState s, int i, id<float>) {
return (int)lua_tonumber(s, i);
}
There are a couple of alternative ways to solve this. To avoid repeatedly adding overloads, boost::enable_if
could be useful. But as long as you have only two special cases for int and float, i would keep it simple and just repeat that one call to lua_tonumber
.
Another pattern that avoids enable_if
and still avoids repeating the overloads is to introduce a hierarchy of type flags - change id
to the following, and keep the code within getC
the same as above. I would use this if there tends to be more cases that need special handling:
template<typename T> struct tostring { };
template<typename T> struct tonumber { };
template<typename T> struct id : tostring<T> { };
template<> struct id<int> : tonumber<int> { };
template<> struct id<float> : tonumber<float> { };
id
needs to be defined outside the class template now, because you cannot explicitly specialize it within the template. Then change the overloads of the helper function to the following
// make the general version convert to string
template<typename T>
T getCConvert(LuaState s, int i, tostring<T>) {
return (T)lua_tostring(s, i);
}
// special versions for numbers
template<typename T>
T getCConvert(LuaState s, int i, tonumber<T>) {
return (T)lua_tonumber(s, i);
}
The specializations would then determine the "configuration" of what should use strings and what number conversion.
I'm not familiar with Lua, but I don't think it matters in this case...
The return of lua_toString is clearly a char*
which means that you're getting the address of the value and then attempting to convert that address to a float. Have a look at strtod
to see how to do this more correctly or, as sellibitze noted, use a stringstream.
I never touched lua
or its engine before, but it seems to me that you are misusing lua_tostring
. This is its signature:
const char *lua_tostring (lua_State *L, int index);
Obviously, this function returns a const char*
. In case of T == int
, C/C++ allow what is called reinterpret_cast
from a pointer
into int
. This conversion is meaningless in case of T == float
. I think you have to take the returned c-string, then convert it into a number using atoi
or atof
depending on the type. The problem happens here:
res = (T)lua_tonumber(luaState, -1);
Because as we said, pointers can be converted into integers
in C/C++ in a meaningful way, unlike floats
.
Using memcpy() to make the assignment will avoid the compiler error.
char *str = lua_tostring(luaState, -1)
memcpy(&res, &str, sizeof(res));
However, the string returned by lua_tostring()
is no longer valid after the call to lua_pop()
. The string really needs to be copied into another buffer.
Even though the Lua website says that, and it makes logical sense, when testing I found that the pointer remained valid even after the state was closed. While he's right that you really should copy the string if you want to keep it around after your Lua function returns, it's probably not his problem.
精彩评论