Rena
Rena

Reputation: 646

C++: using templates to collapse repetitive code with different types and functions

I'm writing a library that allows Lua to read and write arbitrary bytes in a block of memory as different types and byte orders. I've used C++ templates to avoid repetition to some degree, but I haven't been able to avoid it entirely:

/* Read one or more sequential values from the blob and push to the Lua stack
 * using lua_pushinteger().
 * Lua: the Lua state to push to.
 * offset: The byte offset to read from.
 * count: The number of values to read.
 * This method is called from Blob::read() which is called from Lua methods.
 * For offsets outside the valid range, it returns nil.
 */
template <typename T>
lua_Integer Blob::readi(lua_State *Lua, lua_Integer offset, lua_Integer count) {
    if(offset < 0) offset += this->size;
    else offset--; //make 0-based

    for(int i=0; i<count; i++) {
        if(offset >= 0 && offset < this->size) {
            T value = *(&this->data[offset + this->pageOffset]);
            lua_pushinteger(Lua, value);
            offset += sizeof(T);
        }
        else lua_pushnil(Lua);
    }
    return count;
}

/* Same as readi but uses lua_pushnumber
 * even with templates we end up duplicating logic :|
 */
template <typename T>
lua_Integer Blob::readf(lua_State *Lua, lua_Integer offset, lua_Integer count) {
    if(offset < 0) offset += this->size;
    else offset--; //make 0-based

    for(int i=0; i<count; i++) {
        if(offset >= 0 && offset < this->size) {
            T value = *(&this->data[offset + this->pageOffset]);
            lua_pushnumber(Lua, value);
            offset += sizeof(T);
        }
        else lua_pushnil(Lua);
    }
    return count;
}

When we want to read a float or double value, we have to use lua_pushnumber(lua_State *Lua, lua_Number num) instead of lua_pushinteger(lua_State *Lua, lua_Integer num). (Usually lua_Integer is a typedef of some integral type such as int32_t, and lua_Number is a typedef of double.) So I don't know how I can avoid duplicating this function using templates, since lua_pushinteger and lua_pushnumber accept different types.

Additionally, I want to add support for specifying the byte order (using functions from endian.h): you can specify that the values being read are assumed to be in big endian order, little endian, or native (whichever the host machine is). Again, I don't know how to achieve this with templates, since the byte order functions deal with different types.

Upvotes: 1

Views: 213

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275385

void lua_push_anything( lua_State* s, lua_Integer i ) {
  lua_push_integer( s, i );
}
void lua_push_anything( lua_State* s, lua_Number i ) {
  lua_push_number( s, i );
}
void lua_push_anything( lua_State* s, bool b ) {
  lua_push_boolean( s, b ); // actually takes an `int`.
}
void lua_push_anything( lua_State* s, std::string s ) {
  lua_pushlstring( lua_State* s, s.c_str(), s.size() );
}

this solves the type problem -- you have a single override that dispatches to the correct extern "C" lua_* function.

You can deal with endianness in similar ways if you need to.

Upvotes: 2

Pradhan
Pradhan

Reputation: 16737

Use a traits class like this:

template <typename T>
class ReadTraits
{
//Has a typedef-ed callable type called ReadFunc.
};

You would specialize this for the types T you want to be able to read in. Now, your read function would look like this :

template <typename T>
lua_Integer Blob::read(lua_State *Lua, lua_Integer offset, lua_Integer count) {
    if(offset < 0) offset += this->size;
    else offset--; //make 0-based

    for(int i=0; i<count; i++) {
        if(offset >= 0 && offset < this->size) {
            T value = *(&this->data[offset + this->pageOffset]);
            using ReadData = typename ReadTraits<T>::ReadFunc;
            ReadData(Lua, value);
            offset += sizeof(T);
        }
        else lua_pushnil(Lua);
    }
    return count;
}

Upvotes: 1

Related Questions