Search

13.8 — Partial template specialization for pointers

In previous lesson 13.4 -- Template expression parameters, we took a look at a simple templated Storage class:

We showed that this class had problems when template parameter T was of type char* because of the shallow copy/pointer assignment that takes place in the constructor. In that lesson, we used full template specialization to create a specialized version of the Storage constructor for type char* that allocated memory and created an actual deep copy of m_value. For reference, here’s the fully specialized char* Storage constructor and destructor:

While that worked great for Storage<char*>, what about other pointer types (such as int*)? It’s fairly easy to see that if T is any pointer type, then we run into the problem of the constructor doing a pointer assignment instead of making an actual deep copy of the element being pointed to.

Because full template specialization forces us to fully resolve templated types, in order to fix this issue we’d have to define a new specialized constructor (and destructor) for each and every pointer type we wanted to use Storage with! This leads to lots of duplicate code, which as you well know by now is something we want to avoid as much as possible.

Fortunately, partial template specialization offers us a convenient solution. In this case, we’ll use class partial template specialization to define a special version of the Storage class that works for pointer values. This class is considered partially specialized because we’re telling the compiler that it’s only for use with pointer types, even though we haven’t specified the underlying type exactly.

And an example of this working:

This prints the value:

5
7

When myintptr is defined with an int* template parameter, the compiler sees that we have defined a partially specialized template function that works with any pointer type, and instantiates a version of Storage using that template. The constructor of that class makes a deep copy of parameter x. Later, when we change x to 9, the myintptr.m_value is not affected because it’s pointing at its own separate copy of the value.

If the partial template specialization class did not exist, myintptr would have used the normal (non-partially-specialized) version of the template. The constructor of that class does a shallow copy pointer assignment, which means that myintptr.m_value and x would be pointing to the same value. Then when we changed the value of x to 9, we would have changed myintptr’s value too.

It’s worth noting that because this partially specialized Storage class only allocates a single value, for C-style strings, only the first character will be copied. If the desire is to copy entire strings, a specialization of the constructor (and destructor) for type char* can be fully specialized. The fully specialized version will take precedence over the partially specialized version. Here’s an example program that uses both partial specialization for pointers, and full specialization for char*:

This works as we expect:

5
7
Alex

Using partial template class specialization to create separate pointer and non-pointer implementations of a class is extremely useful when you want a class to handle both differently, but in a way that’s completely transparent to the end-user.

13.x -- Chapter 13 comprehensive quiz
Index
13.7 -- Partial template specialization

21 comments to 13.8 — Partial template specialization for pointers

  • nagarajsherigar

    i am trying to create an object of type double from an object of type int.Not getting how to do it

    • Alex

      I don’t see where you’re creating an object of type double or int. I assume test is supposed to be a Test<int> and test3 is supposed to be a Test<double>. If that’s the case, then no, that’s not allowed, because Test<double> doesn’t have a constructor that accepts objects of type Test<int>

  • Rohit

    Hi Alex!
    Great Tutorials. Need some help.

    1) Storage<char*>::Storage(char* value) constructor belongs to which class, Storage or Storage<T*> ?
    2) Isn’t the statement while(value[length] != 0) statement be while(value[length] != ”) ?
    3) strcpy_s() showing error  "strcpy_s was not declared in the scope" even I included the headerfile <cstring>.

    • Alex

      1) Storage is the class the constructor belongs to.
      2) It’s fine as is. The null terminator has ascii value 0. It’s probably better to use ‘\0’ instead to make the intent clearer though.
      3) I’ve updated the example to remove the strcpy_s since it was extraneous anyway.

  • The Long

    Hi Alex. Thank you for the amazing tutorial.
    Currently, I’m using VC 2015 and I can’t compile the following code:
    [code]
    template <typename T>
    class Storage<T*>       // got complain here
    [/code[
    the compiler always complains that the syntax is not right, that I’m using the old style formal list. Is there anyway to work around this?

    • Alex

      It works fine for me on VS2015. Make sure you included the base template definition for Storage above the specialized version, so the compiler knows what you’re specializing.

  • Dominik

    In the last example, in line 108 there is a comment "//This will print garbage". Is this correct?

  • Trey

    Is it possible to have a default template like:

  • Mauricio Mirabetti

    Dear Alex,
    After your first example of code using Storage<int*>, there’s an information missing:
    "This prints the value:
    5 // this is missing
    7"

    Also, on the example showing the use of full specialized Storage<char *> as well, you wrote:
        // If intptr7 did a pointer assignment on x,
      where it should be
          // If myintptr did a pointer assignment on x,
        
    Best regards.

    Mauricio

  • piciurica

    Hi All,

    Could someone, please, explain me this sentence: “If cIntPtrStorage had used the non-pointer version of Storage, it would have done a pointer assignment — and when we changed the value of x, we would have changed cIntPtrStorage’s value too.” ?

    I don’t see any pointer assignment in the non-pointer version of the Storage, thus I think that this sentence is confusion-prone. Besides, I run the code with non-pointer Storage version, and I still got 7, which I find logical, since in this version the parameters are passed by value.

    In order to change the value of x, the constructor of the pointer version of Storage should implement the pointer assignment instead of the new allocation. Am I right?

    Many thanks,
    Irina

    • Alex

      Yes, it’s a little confusing, but I’m not sure how to word it any more clearly.

      Here’s the definition for the non-pointer constructor of Storage

      If T is of type int*, then this becomes:

      And now this isn’t a pass by value, but rather a pass by address, along with a pointer copy in the body of the constructor.

      This means that m_value would be pointing to the same value as the argument passed in. That means if we change the value of the argument, we’re changing the value that m_value is pointing at too, and thus print() print that new value, not the original value.

      The partially specialized pointer version avoids this problem by dynamically allocating memory for a copy of the argument, so that any changes to the argument do not affect the value that m_value is pointing at.

      Is there a better way to express this?

      • Mauricio Mirabetti

        Alex, I understood the question of piciurica. I had to read it a few times to understand as well. If I may suggest, there are two paragraphs where some confusion can be made, depending on how you read it, so I’d write them more or less like this:

        "If T is any pointer type other than char*, then the non-pointer version of template is used (class Storage<T>), and we run into the problem of the constructor doing a pointer assignment instead of making an actual deep copy of the element being pointed to."

        "The fact that we got a 7 here shows that myintptr used the pointer version of Storage - as it was supposed to - which allocated its own copy of the int. If we hand’t created a pointer version (class Storate <T*>), myintptr would have used the non-pointer version of Storage, and it would have done a pointer assignment -- and when we changed the value of x (x = 9;), we would have changed myintptr’s value too."

        Just a suggestion, thou.
        Best regards.

        Mauricio

  • in the “Partial template specialization for pointers” section, the point of specializing a version for pointer type was to deal with the issue of shallow/deep copy on constructor call. However you could imagine the following (pointer to pointer):

    • Alex

      You can delete member functions, but unfortunately there’s no way to tell C++ to delete a partially specified template, so that it can’t be instantiated.

      If you want to prevent someone from instantiating a template of a certain type, the best thing to do at current (as of C++14) is to provide a partial or full specialization for the template, and throw an assert in the constructor. For example, to prevent people from using Storage with objects of type T**, you could do this:

Leave a Comment

Put C++ code inside [code][/code] tags to use the syntax highlighter