January 16th, 2025

In a C++ class template specialization, how can I call the unspecialized version of a method?

Suppose you have some class

template<typename T>
struct Producer
{
    T Produce();
};

and suppose you want to specialize it for one particular type, but you want your specialization to call the unspecialized version too.

template<>
struct Producer<void>
{
    void Produce()
    {
        Log("Producing void!");
        // I want to call Producer<T>::Produce()
        // as if this specialization didn't exist
        Producer::Produce(); // this doesn't work
    }
};

The idea is that you want the specialized Producer<void>::Produce() method to behave the same as an unspecialized Producer<void>::Produce(), but do a little extra logging.

What is the magic syntax for calling the unspecialized version of a template method?

This is a trick question. There is no magic syntax for this. There is no way to talk about a template that doesn’t exist but “would exist” if a specialization were not present. Template specialization is not derivation. Specializing a class template means “For this case, I want the class to look like this.” It is not an override or overload or derivation; it is a definition. The definition of Producer<void> is the class you defined. There is no syntax for saying, “Give me Producer<void> as it would have been in the absence of this specialization,” and your redefinition does not implicitly derive from anything.

There are number of possible workarounds to this.

Probably the most common one is to move everything to a base class, have the primary template derive from the base class, and then override the method in the specialization.

template<typename T>
struct ProducerBase
{
    T Produce();
};

template<typename T>
struct Producer : ProducerBase<T>
{
};

template<>
struct Producer<void> : ProducerBase<void>
{
    using ProducerBase = typename Producer::ProducerBase;

    void Produce()
    {
        Log("Producing void!");
        ProducerBase::Produce();
    }
};

You might be in a case where the Producer class comes from a library you do not control. In that case, you can still follow the pattern, but using the library class as the base class.

template<typename T>
struct Producer
{
    T Produce();
};

template<typename T>
struct MyProducer : Producer<T>
{
};

template<>
struct MyProducer<void> : Producer<void>
{
    using Producer = typename MyProducer::Producer;

    void Produce()
    {
        Log("Producing void!");
        Producer::Produce();
    }
};

// and your code uses MyProducer everywhere
Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

3 comments

Discussion is closed. Login to edit/delete existing comments.

  • Sunstone Hiveware

    There is another way to handle joint code that is used across multiple types.
    1) Distill out the template arguments that identify type subsets.
    2) Write implementations for these subsets of template arguments.
    3) Pass in any parameters to these subsets in their constructors. Avoid passing in all the top type. Only the type sets that are relevant to these subsets. These subsets get dynamically initialized at startup meaning lvalues get established, but not rvalues. This allows you to hide types at lower layers making it not necessary to either destroy the app's full set of types, or compromise on...

    Read more
  • Solomon Ucko

    If you don’t need to deal with getting subclassed, I guess another option could be to have both the unspecialized and specialized versions call out to a helper method that does the actual work?

    • Sunstone Hiveware

      I don't divide instantiation up between specialized and unspecialized. Everything should be specialized to use templates properly. As I see it, un-specialization is merely a corner case, like for use like with logging. Template parameter defaults are another.
      The beauty of my family subsets approach is you only instantiate the exact set of templates your project needs. These template family subsets get implemented without being instantiated. They then get template instantiated subsequently either explicitly or implicitly. I prefer explicit template instantiation because it makes discovering missing implementation errors easier to locate. Doing it this way gets lvalues dynamically instantiated at...

      Read more