Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my template specialization get compiled if it doesn't get executed?

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:

  1. One must be able to define "content holder" classes that hold an arbitrary number of vectors, each holding values of different types. E.g. IntHolder could hold a vector<int>, FloatAndBoolHolder could hold a vector<float> and a vector<bool>, and so on.
  2. Content holder classes must have a 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

like image 377
suszterpatt Avatar asked Nov 11 '22 09:11

suszterpatt


1 Answers

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;
    }
};
like image 75
D Drmmr Avatar answered Nov 15 '22 07:11

D Drmmr