C++ Template Specialisation and Deduction: Predict Which Case Wins

Predict which C++ template definition wins by separating class specialisation, function overload resolution, ordinary deduction, and C++17 CTAD.

KnowledgeGate Team

Exam prep & CS education

Updated 30 Oct 20265 min read
C++ Template Specialisation and Deduction: Predict Which Case Wins

A generic template looks simple until one type needs different behaviour, a pointer should choose another path, or the compiler deduces a type you did not expect. Class and function template specialisation follow different rules, while C++17 class template argument deduction selects class template arguments from constructor syntax. To predict the selected definition, list candidates, deduce template arguments, and rank the viable cases before compiling.

1. C++ template specialisation and deduction solve different jobs

Mechanism

Job

Primary template

Provides the fallback definition

Full class specialisation

Handles one exact template argument list

Partial class specialisation

Handles an argument pattern such as T*

Function template overload

Adds a competing callable pattern

Template argument deduction

Finds missing template arguments from syntax

Ordering or overload resolution

Chooses which viable definition wins

Specialisation changes implementation for selected arguments; deduction discovers arguments. Neither is a runtime if: 7 versus 9 does not change the Serializer<int> definition.

Place templates in the wider Coding & DSA sequence. Templates in C++: Function and Class Templates with Runnable Examples establishes function, class and non-type template fundamentals; specialisation, overload resolution and deduction-guide ordering answer the narrower question of which definition wins.

2. Full and partial class template specialisation with Serializer

This class-template family has three definitions:

cpp
template<class T>
struct Serializer {
    static std::string write(const T&) { return "generic"; }
};

template<>
struct Serializer<bool> {
    static std::string write(bool value) {
        return value ? "true" : "false";
    }
};

template<class T>
struct Serializer<T*> {
    static std::string write(T* value) {
        return value == nullptr ? "null" : "pointer";
    }
};

With int n=7; int* p=&n;, trace the template-id first:

  1. Serializer<int>::write(7) matches no specialisation. The primary returns "generic".

  2. Serializer<bool>::write(false) exactly matches the full specialisation. It returns "false".

  3. Serializer<int*>::write(p) matches T* with T=int. The partial specialisation returns "pointer".

  4. Serializer<int*>::write(nullptr) names that same Serializer<int*> class first. nullptr then converts to the already known int* parameter type, and the function returns "null".

The primary preserves the default, the full specialisation isolates one type, and the partial controls every pointer-shaped type.

3. Class template matching: exact type details decide the winner

Template-id

Selected definition

Reason

Serializer<int>

primary

int matches neither specialisation

Serializer<bool>

full specialisation

The exact argument list is bool

Serializer<int*>

partial specialisation

The pointer pattern matches with T=int

Serializer<const int*>

partial specialisation

The pointer pattern matches with T=const int

Serializer<bool*>

partial specialisation

The argument is a pointer, not exact bool

Serializer<const bool>

primary

const bool is not exact bool

Serializer<int[3]>

primary

An array template argument is not a pointer template argument

Use an exact full specialisation when it exists. Otherwise choose the uniquely most specialised matching partial, or the primary. Equally specialised matching partials are ambiguous.

Call-site conversions do not rewrite the class template-id. Serializer<const bool> does not become Serializer<bool>, and Serializer<int[3]> does not decay to Serializer<int*> merely because arrays often decay in function calls.

Serializer selection: bool uses full specialisation, int* uses pointer partial specialisation, and int uses the primary templ

4. Function templates use overloads, not partial specialisation

Function templates may be overloaded or fully specialised, but cannot be partially specialised. For a parameter shape such as T*, write another overload:

cpp
template<class T>
std::string route(T) { return "generic"; }

template<class T>
std::string route(T*) { return "pointer-template"; }

std::string route(const char*) { return "text-overload"; }

Given int n=42; int* p=&n; const char* text="kg";, trace every candidate:

  • route(42): only route(T) is viable. It deduces T=int and returns "generic".

  • route(p): both templates are exact matches; the const char* overload is not viable. route(T*) is the more specialised template, deduces T=int, and returns "pointer-template".

  • route(text): both templates and the non-template are exact matches. route(T*) is more specialised than route(T), then the non-template wins the remaining tie and returns "text-overload".

An explicit specialisation has a subtle relationship with overload resolution:

cpp
template<class T>
std::string pick(T) { return "primary"; }

template<>
std::string pick<int*>(int*) { return "specialisation"; }

template<class T>
std::string pick(T*) { return "pointer-overload"; }

For int*, overload resolution selects pick(T*). The explicit specialisation is not a separate candidate. Use overloads for call shapes; specialise only with an intentional primary relationship.

5. Template deduction and C++17 CTAD: compute the type first

Function deduction and class template argument deduction (CTAD) have different contexts. In route(p), T* against int* yields T=int. In route(nullptr), T* cannot be deduced from std::nullptr_t; the by-value primary deduces that type and returns "generic". Deduction does not first convert nullptr.

Now add a user-defined deduction guide:

cpp
template<class T>
struct Box {
    T value;
    explicit Box(T input) : value(std::move(input)) {}
};

Box(const char*) -> Box<std::string>;

Box a{42};
Box b{"kg"};

The implicit constructor guide makes a a Box<int> containing 42. The user-defined guide makes b a Box<std::string> containing "kg", not a const char*. Confirm both deductions:

cpp
static_assert(std::is_same_v<decltype(a), Box<int>>);
static_assert(std::is_same_v<decltype(b), Box<std::string>>);

CTAD selects Box<std::string> before construction. It neither creates a specialisation nor inspects the runtime contents of "kg".

Two-lane C++17 CTAD pipeline: Box a{42} deduces Box<int>, while Box b{"kg"} uses a guide to deduce Box<std::string> and stores "kg".

6. C++ template traps: diagnose the type before changing the code

Symptom

Why it happens

Correct move

Attempting function-template partial specialisation

The language does not allow it

Write a function-template overload

Expecting const bool to hit Serializer<bool>

const remains in the explicit template-id

Specialise deliberately or use the primary

Expecting an array template argument to match T*

The template-id contains an array type

Handle an array pattern explicitly if needed

Assuming nullptr deduces a pointer type

Deduction does not convert std::nullptr_t

Supply a pointer type or accept the primary

Defining an explicit specialisation after an earlier use

The primary may already have been instantiated

Declare the specialisation before the first relevant use

Creating overlapping partial specialisations

No match may be uniquely best

Redesign for one winner

Debug in order: write the exact type; list viable templates and overloads; deduce without invented conversions; rank survivors; confirm with static_assert, a diagnostic, or a minimal test.

Runtime virtual dispatch chooses an override through a polymorphic object. C++ Function and Class Templates: Build One Type-Safe Range Pattern builds a reusable range pattern from generic functions and classes. Once candidate templates exist, exact types, partial ordering and overload resolution determine the winner.

7. How interviews and exam-style questions test template control

Representative questions ask for the seven Serializer results, an illegal function partial specialisation, the winning route overload, the type from Box{"kg"}, or a repair for ambiguous class partials.

For route(nullptr), the exact type is std::nullptr_t; T* deduction fails; the by-value primary deduces T=std::nullptr_t. Answer: "generic".

For Serializer<bool*>, the template-id matches T* with T=bool. A non-null pointer returns "pointer", not "true" or "false".

8. C++ template specialisation: the short version and next step

Recall four steps: identify class or function template; write the exact type; deduce; then apply class-specialisation ordering or overload resolution. If the path is unclear, simplify the design.

Reproduce the Serializer matrix and explain why Serializer<const bool> uses the primary. Then predict route(42), route(&n), route(text), and route(nullptr): "generic", "pointer-template", "text-overload", and "generic".

Use C++ Programming for focused language study, or Coding For Placements for wider coding practice. Always write the candidates and exact types before asking the compiler.