Original Code
template<typename T>
struct MetaData
{
static std::string data;
};
With thre requirement that you add something like this to a source file for every class you require metadata for. Failing to do this will generate a link time error.
struct MetaData
{
static std::string data;
};
std::string MetaData<int>::data;
std::string MetaData<MyClass>::data;
The original code using this kind of method would look something like this:
std::string MetaData<MyClass>::data;
template <class T>
static void OpenLib(lua_State* L)
{
if (MetaData<T>::myTable && MetaData<T>::myTableName)
{
luaL_openlib(L, MetaData<T>::myTableName, MetaData<T>::myTable, 0);
}
}
static void OpenLib(lua_State* L)
{
if (MetaData<T>::myTable && MetaData<T>::myTableName)
{
luaL_openlib(L, MetaData<T>::myTableName, MetaData<T>::myTable, 0);
}
}
Template sepcialisation
Personally I don't like link time errors and prefer compile time errors, so we could try using template specialisation to get a similar result.
template<typename T>
struct MetaData
{
};
template<>
struct MetaData<int>
{ static std::string data() { return "foo"; }
}
template<>
struct MetaData<int>
{ static std::string data() { return "bar"; }
}
This time you'll get a compile time failure if you fail to define the MetaData for a class that you are using. However there's still a lot of boilerplate code, which I don't like. Secondly the values can't be changed at runtime, which was a requirement of the original post.
The code would then look something like this:
struct MetaData
{
};
template<>
struct MetaData<int>
{ static std::string data() { return "foo"; }
}
template<>
struct MetaData<int>
{ static std::string data() { return "bar"; }
}
template <class T>
static void OpenLib(lua_State* L)
{
if (MetaData<T>::myTable() && MetaData<T>::myTableName())
{
luaL_openlib(L, MetaData<T>::myTableName(), MetaData<T>::myTable(), 0);
}
}
static void OpenLib(lua_State* L)
{
if (MetaData<T>::myTable() && MetaData<T>::myTableName())
{
luaL_openlib(L, MetaData<T>::myTableName(), MetaData<T>::myTable(), 0);
}
}
Singleton
Another trick we can use to avoid needing to define static variables in a .cpp file is to use metadata that uses singleton functions for each variable. Then the function static variable is created inside the function, (on first function use I think) and lives until the end of the application.
template<typename T>
struct MetaData
{
static std::string& data()
{
static std::string data_("");
return data_;
}
};
As we return a non-const reference we can still change the value at run-time, like this:
struct MetaData
{
static std::string& data()
{
static std::string data_("");
return data_;
}
};
MetaData<int>::data() = "Omg Froobles";
However some people, (myself included) dont really like the use of singletons (or global variables) as they can make code quite hard to test.
The code would then look something like this:
template <class T>
static void OpenLib(lua_State* L)
{
if (MetaData<T>::myTable() && MetaData<T>::myTableName())
{
luaL_openlib(L, MetaData<T>::myTableName(), MetaData<T>::myTable(), 0);
}
}
static void OpenLib(lua_State* L)
{
if (MetaData<T>::myTable() && MetaData<T>::myTableName())
{
luaL_openlib(L, MetaData<T>::myTableName(), MetaData<T>::myTable(), 0);
}
}
(Almost) Template free type map.
There's an alternative to using templates as a way into the C++ type system, and that istype_info
(Well there's also polymorphism, but we'll ignore that option for now).
If we go down that route we can reduce the usage of templates and global/singleton variables significantly.
struct ClassMetaData
{
std::string data;
};
struct TypeInfoCompare
{
bool operator()(const std::type_info* i1, const std::type_info* i2) const
{
return i1->before(*i2);
}
};
std::map<const std::type_info*, ClassMetaData, TypeInfoCompare> metaDataMap;
Using this design I'd probably produce code that looks like this:
{
std::string data;
};
struct TypeInfoCompare
{
bool operator()(const std::type_info* i1, const std::type_info* i2) const
{
return i1->before(*i2);
}
};
std::map<const std::type_info*, ClassMetaData, TypeInfoCompare> metaDataMap;
typedef std::map<const std::type_info*, ClassMetaData, TypeInfoCompare> MetaDataMap;
template<typename T>
static void OpenLib( const MetaDataMap & metadata, lua_State* L)
{
return OpenLibCore( metadata[ &typeinfo(T) ] );
}
static void OpenLibCore( ClassMetaData & m, lua_State *L )
{
if ( m.myTable && m.myTableName )
{
luaL_openlib(L, m.myTableName, m.myTable, 0);
}
}
And while you now need to pass in an additional parameter to the template<typename T>
static void OpenLib( const MetaDataMap & metadata, lua_State* L)
{
return OpenLibCore( metadata[ &typeinfo(T) ] );
}
static void OpenLibCore( ClassMetaData & m, lua_State *L )
{
if ( m.myTable && m.myTableName )
{
luaL_openlib(L, m.myTableName, m.myTable, 0);
}
}
OpenLib
function, these functions are now easily testable. And if you really hate that additional member you can make the MetaDataMap a global variable or singleton.
No comments:
Post a Comment