Search

14.5 — Class template specialization

In the previous lesson on template specialization, we saw how it was possible to specialize member functions of a template class in order to provide different functionality for specific data types. As it turns out, it is not only possible to specialize member functions of a template class, it is also possible to specialize an entire class!

Consider the case where you want to design a class that stores 8 objects. Here’s a simplified class to do so:

Because this class is templated, it will work fine for any given type:

This example prints:

0
1
2
3
4
5
6
7
false
true
true
true
false
true
true
true

While this class is completely functional, it turns out that the implementation of Storage8<bool> is much more inefficient than it needs to be. Because all variables must have an address, and the CPU can’t address anything smaller than a byte, all variables must be at least a byte in size. Consequently, a variable of type bool ends up using an entire byte even though technically it only needs a single bit to store its true or false value! Thus, a bool is 1 bit of useful information and 7 bits of wasted space. Our Storage8<bool> class, which contains 8 bools, is 1 byte worth of useful information and 7 types of wasted space.

As it turns out, using some basic bit logic, it’s possible to compress all 8 bools into a single byte, eliminating the wasted space altogether. However, in order to do this, we’ll effectively need to essentially revamp the class, replacing the array of 8 bools with a variable that is a single byte in size. While we could create an entirely new class to do so, this has one major downside: we have to give it a different name. Then the programmer has to remember that Storage8<T> is meant for non-bool types, whereas Storage8Bool (or whatever we name the new class) is meant for bools. That’s needless complexity we’d rather avoid. Fortunately, C++ provides us a better method: class template specialization.

Class template specialization

Class template specialization allows us to specialize a template class for a particular data type (or set of data types, if there are multiple templated parameters). In this case, we’re going to use class template specialization to write a customized version of Storage8<bool> that will take precedence over the generic Storage8<T> class.

Class template specializations are treated as completely independent classes, even though they are allocated in the same way as the templated class. This means that we can change anything and everything about our specialization class, including the way it’s implemented and even the functions it makes public, just as if it were an independent class. Here’s our specialized class:

First, note that we start off with template<>. The template keyword tells the compiler that what follows is templated, and the empty angle braces means that there aren’t any template parameters. In this case, there aren’t any template parameters because we’re replacing the only template parameter (typename T) with a specific type (bool).

Next, we add <bool> to the class name to denote that we’re specializing a bool version of Storage8.

All of the other changes are just class implementation details. You do not need to understand how the bit-logic works in order to use the class (though here’s a link to the lesson on bitwise operators if you want to figure it out, but need a refresher on how bitwise operators work).

Note that this specialization class utilizes a single unsigned char (1 byte) instead of an array of 8 bools (8 bytes).

Now, when we declare a class of type Storage8<T>, where T is not a bool, we’ll get a version stenciled from the generic templated Storage8<T> class. When we declare a class of type Storage8<bool>, we’ll get the specialized version we just created. Note that we have kept the publicly exposed interface of both classes the same -- while C++ gives us free reign to add, remove, or change functions of Storage8<bool> as we see fit, keeping a consistent interface means the programmer can use either class in exactly the same manner.

We can use the exact same example as before to show both Storage8<T> and Storage8<bool> being instantiated:

As you might expect, this prints the same result as the previous example that used the non-specialized version of Storage8<bool>:

0
1
2
3
4
5
6
7
0
1
2
3
4
5
6
7
false
true
true
true
false
true
true
true

It’s worth noting again that keeping the public interface between your template class and all of the specializations identical is generally a good idea, as it makes them easier to use -- however, it’s not strictly necessary.

14.6 -- Partial template specialization
Index
14.4 -- Expression parameters and template specialization

15 comments to 14.5 — Class template specialization

  • DOBRESCU Mihai

    Hi,

    The second example doesn't compile because the "[]" operator has not been redefined in the specialized class. The setter and the getter have to be used. This is the full code:


    #include <iostream>

    using namespace std;

    template <typename T>
    class Storage8
    {
    private:
    T m_tType[8];

    public:
    void Set(int nIndex, const T &tType)
    {
    m_tType[nIndex] = tType;
    }

    const T& Get(int nIndex)
    {
    return m_tType[nIndex];
    }
    };

    template <> // the following is a template class with no templated parameters
    class Storage8<bool> // we're specializing Storage8 for bool
    {
    // What follows is just standard class implementation details
    private:
    unsigned char m_tType;

    public:
    void Set(int nIndex, bool tType)
    {
    // Figure out which bit we're setting/unsetting
    // This will put a 1 in the bit we're interested in turning on/off
    unsigned char nMask = 1 << nIndex;

    if (tType) // If we're setting a bit
    m_tType |= nMask; // Use bitwise-or to turn that bit on
    else // if we're turning a bit off
    m_tType &= ~nMask; // bitwise-and the inverse mask to turn that bit off
    }

    bool Get(int nIndex)
    {
    // Figure out which bit we're getting
    unsigned char nMask = 1 << nIndex;
    // bitwise-and to get the value of the bit we're interested in
    // Then implicit cast to boolean
    return m_tType & nMask;
    }
    };

    int main()
    {
    // Define a Storage8 for integers (instantiates Storage8<T>, where T = int)
    Storage8<int> cIntStorage;

    for (int nCount=0; nCount<8; nCount++)
    cIntStorage.Set(nCount, nCount);

    for (int nCount=0; nCount<8; nCount++)
    std::cout << cIntStorage.Get(nCount) << std::endl;

    // Define a Storage8 for bool (instantiates Storage8<bool> specialization)
    Storage8<bool> cBoolStorage;
    for (int nCount=0; nCount<8; nCount++)
    cBoolStorage.Set(nCount, nCount & 3);

    for (int nCount=0; nCount<8; nCount++)
    std::cout << (cBoolStorage.Get(nCount) ? "true" : "false") << std::endl;

    return 0;
    }

    Also, the output contains only one column of digits - I do not know why they were repeated in the version printed in the lesson. This is the correct output:


    0
    1
    2
    3
    4
    5
    6
    7
    false
    true
    true
    true
    false
    true
    true
    true

  • Vinay Ch

    Hi,
    I have a doubt.
    Can we specialize a template class for a templated class?

    template
    class Storage8<MyClass>
    {
    ---
    ---
    };

    Here MyClass is a templated class. Is the above valied? Where do we have to mention
    template for MyClass?

  • Nirbhai

    Hi! I like this Tutorial, It is very good. I did not understand line 13 in section Class template specialization
    unsigned char nMask = 1 << nIndex; why does it need, <<nIndex?
    I am looking for a compiler for c++ to compile a program. from where I can get it? Please reply, Thank you.

  • homenirbhai61

    Hi! I like this Tutorial, It is very good. I did not understand line 13 in section Class template specialization
    unsigned char nMask = 1 << nIndex; why does it need, <<nIndex?
    I am looking compiler for c++ to run these program, from where I can get it? Please reply, Thank you.

  • Flurite

    In the last example, you used the indexing operator for cIntStorage, which you never declared/defined in the declaration of the class.

  • Abhishek

    Hi Alex,

    Why is it so that for creating function template specialization we don't need to have template<> keyword
    Like you have explained in previous tutorial :

     
    void Storage<double>::Print()
    {
    std::cout << std::scientific << m_tValue << std::endl;
    }
    

    But for class template specialization we need the template <> keyword

    template <>
    class Storage8<bool> 
    { ...};
    
    • Tom Backton

      **************Please ignore the previous comment...I forgot the tags...*******
      ******************************************************************************

      Few questions...
      1. Can I specialize a class template's member function? For example, let's say I have a class template like this:

      template<class A, class B, class C> MyClass;

      For B=int, some member functions need to have special implementations. Can I define

      template<class A, class B, class C> void MyClass<A,B,C>::func() {...}

      and then just define a special implementation like this:

      template<class A, class C> void MyClass<A,int,C>::func() {...}

      It it possible?

      2. MyClass's functions have few different implementations. And the interface is different too. One method is defining each version as a different class, but what if I'd like to use a template instead? in template, impl is the implementation's number. Can I define a general class definition:

      template<class A, class B, int impl> class MyClass { ... };

      And then define a specialization for impl=1 like this:

      template<class A, class B> class MyClass<A,B,1> { ... };

      Is it possible?

      3. If yes, let's say MyClass is a template like this:

      template<class A, class B, class C> class MyClass { ... };

      For A=int, I'd like to specialize this class template:

      template<class B, class C> class MyClass<int,B,C> { ... };

      For A=int and C=double, I'd like to have another specialization: (but for A=int and C!=double I'd like the compiler to use the first specialization)

      template<class B> class MyClass<int,B,double> { ... };

      And I'd also like to have a full specialization like this:

      template<> class MyClass<int,char*,double> { ... };

      Do compilers ready to handle such sets of specializations?

  • About 5 paragraphs above the go to next/previous page buttons, you forgot a semicolon on the html entity & gt ;, writing &gt, in the following:

    ..while C++ gives us free reign to add, remove, or change functions of Storage8<bool&gt as we see fit, keeping a consistent..

    [ Fixed. Thanks for noticing. -Alex ]

  • Nedu
    Hi,
    I am really enjoying this readings.
    i have a clarification here.
    
    04     Storage8<int> cIntStorage;  
    05   
    06     for (int nCount=0; nCount<8; nCount++)  
    07         cIntStorage[nCount] = nCount; 
    
    look at lines 04 & 07
     Is this legal to the compiler ?
     if so why the following gives compiler error.
    
    
    class A
    {
         public:
            int data;
    };
    
    
    int main()
    {
    
      A ob;
            for(int i=0;i<10;i++)
            ob[i]=i;
    
            for(int i=0;i<10;i++)
            cout<<endl<<ob[i]<<endl;
    
      return 0;
    
    }
    
    
    
    
  • Hasti

    Hi,
    I don't understand the meaning of line 15 of the first example in this page(in the main() function), I mean:
    cBoolStorage.Set(nCount, nCount & 3);
    I have problem with the second argument of the Set function? Would you please explain it more?(I know it should be a bool, but how could it be?)
    Herewith, I would like to thank the writer of this tutorial! I always enjoy reading these pages!

    • I just realized I made a mistake in my code and flipped the true/false results. That's been fixed, so take another look now. nCount & 3 produces an integer value. C++ will implicitly cast the result of this expression to an boolean. If the result is 0, the boolean will be false. If the result is non-zero, the boolean will be true. So when is nCount & 3 == 0? When nCount is a number that has it's first and second bits set to 0 (in other words, 0, 4, 8, 12, ...). Consequently, when nCount is one of these numbers, nCount & 3 will be zero, and the boolean will be set to false. When nCount is not one of these numbers, nCount & 3 will be non-zero, which means the boolean will be set to true.

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">