Search

13.5 — Function template specialization

When instantiating a function template for a given type, the compiler stencils out a copy of the templated function and replaces the template type parameters with the actual types used in the variable declaration. This means a particular function will have the same implementation details for each instanced type (just using different types). While most of the time, this is exactly what you want, occasionally there are cases where it is useful to implement a templated function slightly different for a specific data type.

Template specialization is one way to accomplish this.

Let’s take a look at a very simple template class:

The above code will work fine for many data types:

This prints:

5
6.7

Now, let’s say we want double values (and only double values) to output in scientific notation. To do so, we can use a function template specialization (sometimes called a full or explicit function template specialization) to create a specialized version of the print() function for type double. This is extremely simple: simply define the specialized function (if the function is a member function, do so outside of the class definition), replacing the template type with the specific type you wish to redefine the function for. Here is our specialized print() function for doubles:

When the compiler goes to instantiate Storage<double>::print(), it will see we’ve already explicitly defined that function, and it will use the one we’ve defined instead of stenciling out a version from the generic templated class.

The template <> tells the compiler that this is a template function, but that there are no template parameters (since in this case, we’re explicitly specifying all of the types). Some compilers may allow you to omit this, but it’s proper to include it.

As a result, when we rerun the above program, it will print:

5
6.700000e+000

Another example

Now let’s take a look at another example where template specialization can be useful. Consider what happens if we try to use our templated Storage class with datatype char*:

As it turns out, instead of printing the name the user input, storage.print() prints garbage! What’s going on here?

When Storage is instantiated for type char*, the constructor for Storage<char*> looks like this:

In other words, this just does a pointer assignment (shallow copy)! As a result, m_value ends up pointing at the same memory location as string. When we delete string in main(), we end up deleting the value that m_value was pointing at! And thus, we get garbage when trying to print that value.

Fortunately, we can fix this problem using template specialization. Instead of doing a pointer copy, we’d really like our constructor to make a copy of the input string. So let’s write a specialized constructor for datatype char* that does exactly that:

Now when we allocate a variable of type Storage<char*>, this constructor will get used instead of the default one. As a result, m_value will receive its own copy of string. Consequently, when we delete string, m_value will be unaffected.

However, this class now has a memory leak for type char*, because m_value will not be deleted when a Storage variable goes out of scope. As you might have guessed, this can also be solved by specializing the Storage<char*> destructor:

Now when variables of type Storage<char*> go out of scope, the memory allocated in the specialized constructor will be deleted in the specialized destructor.

Although the above examples have all used member functions, you can also specialize non-member template functions in the same way.

13.6 -- Class template specialization
Index
13.4 -- Template non-type parameters

35 comments to 13.5 — Function template specialization

  • Anastasia

    Hi,
    thanks for the lesson, I think I understand how template specialization works. But I struggle a bit with the last example. Not with the implementation itself, but with the reasons to specialize constructors/destructors (possibly other member functions as well) to avoid potential shallow copies and other issues. I mean in this particular example the programmer is aware that the shallow copy is going to be made, so they won't delete pointers pointing to the memory address of the `string`. Why not just set it (`string`) to 'nullptr' instead of wasting time and performance trying to specialize templates for every (potentially dynamically allocated) pointer?
    Of course the pointer also needs to be 'delete'd manually afterwards... I can see that it could be rather error prone, but still, isn't trying to predict every possible usage of a template class (and specializing the functions accordingly) is more troublesome?

    • Alex

      In some cases, the special cases will be obvious (e.g. if your class is doing lots of equality comparisons, maybe you should have a special version for floating point numbers). In other cases, they might not be so obvious, and your program will misbehave or explode when you try something, and you'll only figure out that you need a special case after you've debugged the issue.

      Either way, the primary goal of this lesson is to show off the mechanism, so you can put it in your toolkit. It's there if you need it. You may never.

      • Anastasia

        Thank you for your reply. I understand that it's just an example of the case where this functionality can be useful. I was just wondering whether it's a usual way to handle template classes. I mean trying to make sure it can be safely used with everything. Because it seems that there won't be much point in having it as a template then and it'd be surely still breakable in some way or the other... Nice to know that it's only for special cases, it makes more sense to me this way.

  • Ryan

    I have a few questions?

    1. When finding how long "length" of char* value is, would using std::strlen(value) be more efficient or is it more dangerous to use this instead of than method used in the code above.

    2. When copying value to m_value in the for loop. Would it be possible to use std::strcpy(m_value, value) or is it dangerous to use strcpy?

    3. If I define in main() :
    Storage<char*> hello("Hello");
    hello.print();

    I got a warning from the compiler "C++ forbids converting a string constant to 'char*'."
    It still compiled though. Is this ok to just leave it or should I fix it. If so, how to counter it?

    • 1)
      Neither is dangerous, @std::strlen is most likely implemented the same we as we did manually.

      2)
      It's not dangerous.

      3)
      Make sure you followed lesson 0.10 and 0.11, the warning you're seeing should be an error.
      "Hello" is a constant string literal, char* is not constant, the types are incompatible.

  • limcheling

    This chapter is about a template class's function specialization.
    How about a normal class with template member function's specialization?

    Eg.
    class ABC{
    private:
       T m_Val;
    public:
       template<typename T>
       void assignVal(T &val);
    };

    template<typename T>
    void ABC::assignVal(T &val){ m_Val = val;}

    //if the type of T is an array, then the assignVal() would like to perform some calculations before assigning to m_Val. How to do this function specialization?

    • @ABC is a template class, because it has a template member (@T).
      You can use @<type_traits> (not covered on learncpp) to disable a function during compile time.

      Output
      T is not an array
      T is an array

  • Jon

    For the "Another Example" section, my IDE/compiler (XCode 10.1) doesn't print garbage using the initial main function provided.  It just prints the string as I input it.

    I guess I'm probably getting undefined behavior that just happens to work based on my specific compiler? Haven't been able to get it to fail...

    • Hi Jon!

      > I'm probably getting undefined behavior that just happens to work based on my specific compiler?
      Yep. Modifications to your compiler, compiler settings, or code could change the results.

  • kdart

    In the code snippet just below the "Another example" header, I think you could make the lesson clearer if you changed the name of the object you instantiate to something other than value.  The code is fine ofc, but the naming conflicts convolute the message imo.

  • David B

    For something like this is there a way to specify any type of pointer?

    like

    where T is a pointer to any type
    Also, since we don’t know if m_value is an array or a single object would it be an issue to do

    to cover both possibilities?

    • Alex

      1) We cover how to partially specialize for pointers in lesson 13.8.
      2) No, doing both an array delete and normal delete is likely to lead to issues. You can have your non-pointer template use normal delete, and your partially specialized pointer version use array delete.

  • Help

    Hi Alex,
    I wounder after u delete string shouldnt u point it to nullptr? I mean on the other they are object, so when the object dies it will also die so u only have to delete the memory from heap ( destructor) but when u wanna show shallow copy and create string after u delete it, its a dangling point? cause when u do a shallow copy to a object and after that object dies that string will still be left as a dangling point, i am right? (i am aware we delete before the object dies, but either way)
    (sorry for being pointy just wanna make sure )

    Thanks in advance!:)

    • Alex

      Generally, you should set a pointer to nullptr after deleting it, unless that pointer is going out of memory immediately after being deleted anyway. This includes local variable pointers deleted at the end of functions, as well as member pointers deleted in the destructor.

      Keep in mind that setting a pointer to nullptr won't set other pointers pointing to the same thing to nullptr, so setting a pointer to nullptr will _not_ save you from dangling pointers!

  • KnowMore

    Can you tell me how to specialize the printArray() For Doubles?
    Thanks In Advance ALEX ! :)

    • Alex

      This doesn't work because you're trying to partially specialize a function, which C++ doesn't support (at least as of C++14). Partial template specialization only works for classes, not individual functions. I'm not sure of any way to address what you're trying to do, other than define a partially specialized class StaticArray<double, size>.

      I updated lesson 13.7 with some information about how to solve this issue.

  • The Long

    Hello Alex.
    I tried this program but it did not very well. Every time I run it, I encounter an error message.
    Here is the code:

    and here is what it print out in the console:
    50            
    Hello         // The string I enter
    Hello         // The output string
    And this is the error message in a box I got after it finish printing. And it happens right after "return 0;" executed (before the main() block finish) and cause the program to crash:
    "HEAP CORRUPTION DETECTED: after Normal block (#195) at /* the address of STR */
    CRT detected that the application wrote to memory after end of heap buffer"
    I did manage the dynamic memory and do nothing to the HEAP. What could be the problem?

    • Alex

      You have an off-by-one error in your specialized char* constructor:

      for (int i = 0; i <= length; ++i) m_value[i] = value[i]; Since you already increased length by 1 to account for the null terminator, the <= should be <.

      • Help

        Hi,
        if i have understand things correct i find this error. U dont have any function get_value() in ur class! Also the in the destructor the m_value = nullptr; isn't necessery as the object dies then m_value is destroyed! Also u got a dangling point on ur string as u only delete it and its not a part of object! after u delete[] string; u should string = nullptr; (its not a part of object so u have to do it by urself!)

  • Ola Sh

    Hello Alex,

    Thanks again for your really good tutorials. In your last example, should the strString pointer be deleted with delete[] since it is an array? Also, I tried to run the code below, but it keeps crashing at line 4 (cString.Print()). I have tried using Visual Studio debugging tools with no success. I would appreciate your help. Thanks. (The code works if I collect user input for the strString pointer).

    • Alex

      Updated the missing [] on delete.

      The problem here is that you're assigning string literal "Hello World" to strString (which changes the address of strString) and then later trying to delete it. Since "Hello World" isn't a dynamically allocated string, this causes a crash.

      You probably meant to do a strcpy_s here instead of an assignment.

  • Pavan

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

    Here m_tValue is Private member variable. How is it allowing us to use the value outside the class scope?

    • Alex

      It's not outside the class scope. Storage::Print() is a member function of class Storage, so that function can directly access private members.

  • Reaversword

    You said to get a template specialization working, we need just redefine the function out of the class, with the concrete type we need to specialize the function, in this way:

    But, if we're using a template expression parameter, as in "Buffer" example and we need to specialize a function, how can we do that to specialize the type, but not the nSize?

    I mean:

    basically is about mix two cases described in the lesson. It is possible redefine a function with a concrete type, but with any nSize?.

    • Alex

      It looks like C++ does not allow you to do partial template specialization of member functions (you can for non-member functions though).

      The solution is to partially specialize the whole class, so the member function isn't partially specialized compared to the class it's part of.

  • Kiran C K

    In the line,

    can the specific template's number of type parameters be different than the generic one? Also, if we do not need the expression parameter, can we omit that in the specific version for a type?

    • Alex

      > can the specific template’s number of type parameters be different than the generic one?

      Not if they're coming from the same template class declaration. You could do this with class template specialization though (which I cover in the next lesson).

      > Also, if we do not need the expression parameter, can we omit that in the specific version for a type?

      No, but you're free to not use it.

  • Ashur

    Here is the code taken from this section that gives me an error message when compiled and run. Would appreciate any help in fixing my code. Thanks,
    ___________________________________________________
    #include <iostream>
    #include <cstring>
    #include <sstream>

    using namespace std;

    template <typename T>
    class Storage
    {
    private:
        T m_tValue;
    public:
        Storage(T tValue)
        {
             m_tValue = tValue;
        }

        ~Storage()
        {
        }

        void Print()
        {
            std::cout << m_tValue << std::endl;
        }
    };

    int main()
    {
        using namespace std;

        // Dynamically allocate a temporary string
        char *strString = new char[40];

        // Ask user for their name
        cout << "Enter your name: ";
        cin >> strString;

        // Store the name  
        Storage<char*>::Storage(char* tValue)
          {
        m_tValue = new char[strlen(tValue)+1];
        strcpy(m_tValue, tValue);
          }

        Storage<char*>::~Storage()
          {
            delete[] m_tValue;
          }

        // Delete the temporary string
        delete strString;

        // Print out our value
        strValue.Print();
    }
    _______________________________________________

    Error Message:
    example192.cpp: In function ‘int main()’:
    example192.cpp:40:28: error: expected primary-expression before ‘char’
         Storage<char>::Storage(char tValue)
                                ^
    example192.cpp:40:39: error: cannot call constructor ‘Storage<char>::Storage’ directly [-fpermissive]
         Storage<char>::Storage(char tValue)
                                           ^
    example192.cpp:40:39: note: for a function-style cast, remove the redundant ‘::Storage’
    example192.cpp:46:30: error: no matching function for call to ‘Storage<char*>::~Storage()’
         Storage<char*>::~Storage()
                                  ^
    example192.cpp:46:30: note: candidate is:
    example192.cpp:18:5: note: Storage<T>::~Storage() [with T = char*]
         ~Storage()
         ^
    example192.cpp:18:5: note:   candidate expects 1 argument, 0 provided
    example192.cpp:55:5: error: ‘strValue’ was not declared in this scope
         strValue.Print();

    • Alex

      A few thoughts:
      1) You have your specialized constructor and destructor inside main(). They need to be moved outside.
      2) Why use char* instead of std::string? std::string makes life easier.
      3) strValue was never defined.
      4) You never allocate an instance of your Storage class.

      Once you fix these issues you'll be a lot closer to a working solution. :)

Leave a Comment

Put all code inside code tags: [code]your code here[/code]