First off, the code
vector<T> kvec;
for (ulong kv = 0; kv < ke.count; kv++)
{
T key;
if (typeid(T) != typeid(QUAT32))
{
fread(&key, sizeof(T), 1, fh);
}
else{
shortQuat key16;
fread(&key16, sizeof(shortQuat), 1, fh);
key.Parse(key16);
}
kvec.push_back(key);
}
the following compiler error is thrown at key.Parse(key16);
:
error C2039: 'Parse' : is not a member of 'libm2::VEC3F'
Of course it doesn't; but the problem is that anything with the type of VEC3F shouldn't even get there. Obviously the compiler is using the first type that gets sent to the class that snippet is from, which is fine - but why ignore the conditional?
And yeah, if I comment that line out, it compiles fine - and the conditional works fine at runtime.
So I guess the real question is, what's the best way to deal with this, without having to duplicate an entire class for one specialized type?
To expand on my comment:
The if
runs at runtime, not compile time (the optimizer might remove the branch, but it has to compile in the first place to get that far).
You can use template specialization instead, which is done at compile time (the compiler instantiates a separate copy of the templated function for each type you give it before compiling it). You can provide a specialization to force a different (special) function body for particular types, which is exactly what you want in this case.
Here's an example derived from the code in your question (I'm assuming the double-push_back to kvec
for non QUAT32s is a typo):
template<typename T>
inline void readKey(T& key, FILE* fh)
{
fread(&key, sizeof(T), 1, fh);
}
template<>
inline void readKey<QUAT32>(QUAT32& key, FILE* fh)
{
shortQuat key16;
fread(&key16, sizeof(shortQuat), 1, fh);
key.Parse(key16);
}
// ...
vector<T> kvec;
for (ulong kv = 0; kv < ke.count; kv++)
{
T key;
readKey(key, fh);
kvec.push_back(key);
}
Problem:
Needed to handle a special case in a templated constructor, that needed to be converted for usefulness. But, as I feared, if statements are ignored on compile.
Solution:
Add a second type to the template, and create a type-cast operator in one of the types.
vector<T> kvec;
for (ulong kv = 0; kv < ke.count; kv++)
{
T2 key;
fread(&key, sizeof(T2), 1, fh);
kvec.push_back((T)key);
}
In most cases, the class would be constructed like so:
SomeClass c = SomeClass<typeA,typeA>(fh);
obviously, casting between the same type should be pretty straightforward. But in the case of QUAT32...
SomeClass c = SomeClass<QUAT32,SHORT_QUAT>(fh);
the solution was to just do this:
typedef struct SHORT_QUAT
{
short x;
short y;
short z;
short w;
operator const QUAT32(){
QUAT32 q;
q.x = float(x < 0 ? x + 32768 : x - 32767) / 32767.0f;
q.y = float(y < 0 ? y + 32768 : y - 32767) / 32767.0f;
q.z = float(z < 0 ? z + 32768 : z - 32767) / 32767.0f;
q.w = float(w < 0 ? w + 32768 : w - 32767) / 32767.0f;
return q;
}
}shortQuat;
and then the data that was stored in shortquat form could be cast to the more useful float format without issue.
It just seemed like a waste to have to create an entire duplicate just for a special case. I'd like to think it's the lesser of two ugly.
Another solution is to declare static variable in the template class, that stores previously defined type ID and check it when need to.
#define CNTYPE_NONE 0
#define CNTYPE_STRING 1
#define CNTYPE_SIGNINT 2
template <typename key_t, typename value_t>
class HashTable
{
public:
HashTable() {;}
...................
static int m_nTypeID;
// and later you can check the type by:
void SomeFunc()
{
if(m_nTypeID == CNTYPE_STRING)
{
................
}
else
{
................
}
}
};
typedef HashTable<std::string, std::string> THashTableString;
typedef HashTable<int, int> THashTableSignInt;
THashTableString::m_nTypeID = CNTYPE_STRING;
THashTableSignInt::m_nTypeID = CNTYPE_SIGNINT;
int main()
{
THashTableString strTable;
THashTableSignInt intTable;
.........................................
return 0;
}