tags:

views:

301

answers:

6

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’
+2  A: 

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.

sellibitze
I've amended the code above, the issue is actually with else clause erroring at compile time, it should fall into the first branch of the if statement.
Jamie
Could you provide an example of this being used inside I class, the above seems to be what I'm after but I can't seem to get it to work. Also would you happen to have any reference to using templates in this way my c++ reference doesn't mention this method.
Jamie
A: 

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.

John Cavan
Thanks for the help, however strtod wouldn't help because if should fall down the first branch of the if statement. I want the second branch for conf->getC<std::string>()..... which I probably should have explained earlier. Sorry
Jamie
@jamie - no problem, I saw your edit after I posted.
John Cavan
A: 

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.

AraK
don't you mean "reinterpret_cast" from pointer to int?
sellibitze
@sellibitze There is no reinterpretation of a pointer type to another. The conversion here is char* -> int, which is defined but char* -> float is not allowed. This is simple static_cast.
AraK
reinterpret_cast is not limited to pointer->pointer conversions. (int)(p) is equivalent to reinterpret_cast<int>(p) where p is some pointer. static_cast<int>(p) is not allowed by the standard.
sellibitze
@sellibitze Yes, you are correct :)
AraK
+1  A: 

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.

Johannes Schaub - litb
What's with the C-style casts? lua_tostring seems to return a "char*" or "char const*". Using T=int or T=long -- whichever satisfies sizeof(T)==sizeof(char*) -- will be a reinterpret_cast. I'm almost sure that this is not what Jamie wanted.
sellibitze
For `int`, `lua_tonumber` is used. There won't be a `lua_tostring` involved :)
Johannes Schaub - litb
True. But still ... I probably would have used the functional-style cast. +1 for the otherwise elegant solutions! :)
sellibitze
A function style cast has the same meaning as a c style cast, and i don't like it because it makes things appear ambiguous. I placed the cast to avoid possible warnings of the compilers, like converting long to int (if `lua_tonumber` returns a `long`). The casts aren't necessary other than that according to the original poster :)
Johannes Schaub - litb
thanks for the +1 though, appreciated :)
Johannes Schaub - litb
A: 

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.

gwell
A: 

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.

DeadMG