Почему моя специализация шаблона компилируется, если она не выполняется?

Примечание: я понимаю, что многое из того, что я здесь делаю, будет проще в C++11, но я не могу использовать это в своем проекте.

Я делаю систему управления контентом. Основные требования:

  1. Нужно уметь определять классы "владельца содержимого", которые содержат произвольное количество векторов, каждый из которых содержит значения разных типов. Например IntHolder мог держать vector<int>, FloatAndBoolHolder мог держать vector<float> и vector<bool>, и так далее.
  2. Классы владельца контента должны иметь 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;
    }
};
Другие вопросы по тегам