Почему моя специализация шаблона компилируется, если она не выполняется?
Примечание: я понимаю, что многое из того, что я здесь делаю, будет проще в C++11, но я не могу использовать это в своем проекте.
Я делаю систему управления контентом. Основные требования:
- Нужно уметь определять классы "владельца содержимого", которые содержат произвольное количество векторов, каждый из которых содержит значения разных типов. Например
IntHolder
мог держатьvector<int>
,FloatAndBoolHolder
мог держатьvector<float>
иvector<bool>
, и так далее. - Классы владельца контента должны иметь
get<>()
метод. Аргумент шаблона дляget<>()
это тип. Если у владельца контента есть вектор с этим типом, тоget<>()
должен вернуть значение из этого вектора, в противном случае вызовget<>()
должен генерировать ошибку компилятора. Например, если у меня естьIntHolder
объект, затем вызовget<int>()
на это вернетint
от своего вектора, но зоветget<float>()
на нем будет генерировать ошибку компилятора.
Мне удалось найти решение, которое делает все это. Предупреждение, рекурсия шаблона впереди:
#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];
}
};
Вот как вы используете это:
// 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;
}
Это все прекрасно и модно, и удовлетворяет всем требованиям выше. Однако ошибка компилятора для get<float>()
действительно ужасно Поэтому я попытался ввести еще одну специализацию для Getter
это объясняет случай, когда мы достигли конца иерархии классов и до сих пор не нашли подходящий тип:
// 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;
}
};
Но в этом случае компиляция не выполняется на этом статическом утверждении, даже если я не вызываю get<float>()
, Еще более странно, что если я также уберу статическое утверждение и просто верну 0, код компилируется и запускается без вывода "getter 4"!
Вопрос: что дает? Насколько я понимаю, шаблоны создаются только в случае необходимости, но Getter 4 никогда не выполняется. Почему компилятор создает экземпляр Getter 4?
Живой пример: http://ideone.com/TCSi6G
1 ответ
Компилятор может скомпилировать вашу функцию-член "getter 4", потому что код не зависит от аргументов шаблона. Если вы сделаете код зависимым от аргументов шаблона, компилятор не сможет скомпилировать его, пока вы не создадите его с определенным типом. Простой способ добиться этого - использовать тип в статическом утверждении.
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;
}
};