Note: I understand that much of what I'm doing here would be easier in C++11, but I can't use it in my project.
I'm making a content management system. The basic requirements are:
IntHolder
could hold a vector<int>
, FloatAndBoolHolder
could hold a vector<float>
and a vector<bool>
, and so on.get<>()
method. The template argument for get<>()
is a type. If the content holder has a vector with that type, then get<>()
must return a value from that vector, otherwise the call to get<>()
must generate a compiler error. E.g. if I have an IntHolder
object, then calling get<int>()
on it would return an int
from its vector, but calling get<float>()
on it would generate a compiler error.I managed to come up with a solution that does all this. Warning, template recursion ahead:
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int value = 'A';
// helper struct that saves us from partially specialized method overloads
template < class RequestedType, class ActualType, class TContentHolder >
struct Getter;
// holds a vector of type TContent, recursively inherits from holders of other types
template < class TContent, class TAddContentHolders >
class ContentHolder : public ContentHolder< typename TAddContentHolders::ContentType, typename TAddContentHolders::AdditionalContentTypes >
{
public:
typedef TContent ContentType;
typedef TAddContentHolders AdditionalContentTypes;
private:
typedef ContentHolder< typename TAddContentHolders::ContentType, typename TAddContentHolders::AdditionalContentTypes > ParentType;
public:
vector< ContentType > mVector;
ContentHolder()
{
for ( int i = 0; i < 5; ++i )
{
mVector.push_back( ContentType(value++) );
}
}
virtual ~ContentHolder() {}
template < class RequestedType >
RequestedType get()
{
return Getter< RequestedType, ContentType, ContentHolder < TContent, TAddContentHolders > >::get(this);
}
};
// specialization for ending the recursion
template < class TContent >
class ContentHolder< TContent, bool >
{
public:
typedef TContent ContentType;
typedef bool AdditionalContentTypes;
vector< ContentType > mVector;
ContentHolder()
{
for ( int i = 0; i < 5; ++i )
{
mVector.push_back( ContentType(value++) );
}
}
virtual ~ContentHolder() {}
template < class RequestedType >
RequestedType get()
{
return Getter< RequestedType, ContentType, ContentHolder< ContentType, bool > >::get(this);
}
};
// default getter: forwards call to parent type
template < class RequestedType, class ActualType, class TContentHolder >
struct Getter
{
static RequestedType get(TContentHolder* holder)
{
cout << "getter 1" << endl;
return Getter< RequestedType, typename TContentHolder::ContentType, typename TContentHolder::AdditionalContentTypes >::get(holder);
}
};
// specialized getter for when RequestedType matches ActualType: return value from holder
template < class RequestedType, class TContentHolder >
struct Getter< RequestedType, RequestedType, TContentHolder >
{
static RequestedType get(TContentHolder* holder)
{
cout << "getter 2" << endl;
return holder->mVector[0];
}
};
// specialized getter for end of recursion
template < class RequestedType >
struct Getter< RequestedType, RequestedType, bool >
{
static RequestedType get(ContentHolder< RequestedType, bool >* holder)
{
cout << "getter 3" << endl;
return holder->mVector[0];
}
};
Here's how you use it:
// excuse the ugly syntax
class MyHolder : public ContentHolder< int, ContentHolder< bool, ContentHolder< char, bool > > >
{
};
int main() {
MyHolder h;
cout << h.get<int>() << endl; // prints an int
cout << h.get<bool>() << endl; // prints a bool
cout << h.get<char>() << endl; // prints a char
//cout << h.get<float>() << endl; // compiler error
return 0;
}
This is all fine and dandy, and satisfies all the requirements above. However, the compiler error for the get<float>()
is really ugly. So I tried to introduce another specialization for Getter
that accounts for the case when we reached the end of the class hierarchy and still haven't found a matching type:
// static assert helper
template <bool b>
struct StaticAssert {};
template <>
struct StaticAssert<true>
{
static void test(const string& s) {}
};
template < class RequestedType, class NonMatchingType >
struct Getter< RequestedType, NonMatchingType, bool >
{
static RequestedType get(ContentHolder< NonMatchingType, bool >* holder)
{
cout << "getter 4" << endl;
StaticAssert<false>::test("Type not in list");
return 0;
}
};
But this way, compilation fails on that static assert even if I don't call get<float>()
. Even more bizzarely, if I also remove the static assert and simply return 0, the code compiles and runs without ever printing "getter 4"!
The question: What gives? To my understanding, templates only get instantiated if they will be needed, but Getter 4 is never executed. Why does the compiler instantiate Getter 4?
Live example: http://ideone.com/TCSi6G
The compiler can compile your "getter 4" member function, because the code does not depend on the template arguments. If you make the code dependent on the template arguments, the compiler cannot compile it until you instantiate it with a specific type. An easy way to achieve this is to use the type in the static assert.
template < class RequestedType, class NonMatchingType >
struct Getter< RequestedType, NonMatchingType, bool >
{
static RequestedType get(ContentHolder< NonMatchingType, bool >* holder)
{
cout << "getter 4" << endl;
StaticAssert<sizeof(NonMatchingType) == 0>::test("Type not in list");
return 0;
}
};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With