December Code

C++ templates interview questions, with answers

Templates are how C++ writes code once for many types, and they work very differently from generics in Java or C#: the compiler generates real code for each set of types you use. Interviewers use them to check whether you understand that model and its consequences, from why template code lives in headers to why an error message can run to a hundred lines. Every answer below comes with code that was compiled and run.

The questions start with what a template is, move through specialisation and the kinds of template parameters, and end with the C++20 feature that tames template errors. Then take the free C++ diagnostic — ten questions across every C++ topic in the bank — to see which of these you can explain but not yet predict.

The questions, with answers

  1. 1.What is a template in C++, and how is it different from generics in Java?

    In short: A template is a pattern the compiler instantiates into separate, fully typed code for each set of template arguments; Java generics compile once and erase the type, checking it only at compile time.

    When you use std::vector<int> and std::vector<std::string>, the compiler generates two separate classes, each compiled and optimised for its type, which is why templates have no run-time cost and can work with any type that supports the operations the template uses. Java generics take the opposite approach: one compiled class works on Object references, the type arguments are erased after checking, and primitives must be boxed. The C++ model has costs of its own: every instantiation adds code, compile times grow, and a type that lacks a needed operation produces an error deep inside the template. Templates can also take values as parameters and be specialised for particular types, which erased generics cannot do.

    template <typename T>
    T largest(std::vector<T> v) {
        T best = v[0];
        for (const T& x : v)
            if (best < x)
                best = x;
        return best;
    }
    
    int main() {
        std::vector n{3, 8, 2};
        std::vector<std::string> w{
            "ant", "bee"};
        std::cout << largest(n)
            << ' ' << largest(w);
    }
    // 8 bee
  2. 2.Why are C++ templates usually defined in header files?

    In short: The compiler needs the template's full definition wherever it is instantiated, so a definition hidden in a .cpp file leaves other files unable to generate the code, and the link fails.

    An ordinary function can be declared in a header and defined in one .cpp file, because the compiled definition is found at link time. A template is not code until it is instantiated, and instantiation happens in the file that uses it, with the types that file supplies, so that file must see the template's body. If a function template's definition sits in a .cpp file, other files compile a call to it but nothing generates the instantiation, and the linker reports an undefined reference. The usual answer is to define templates in headers. The alternative is explicit instantiation: define the template in a .cpp file and list the instantiations you support there, as in template int twice<int>(int);, which suits templates used with only a few known types.

    template <typename T>
    T twice(T x) { return x + x; }
    
    template int twice<int>(int);
    
    int main() {
        std::cout << twice(21)
            << ' ' << twice(1.5);
    }
    // 42 3
  3. 3.What is template specialisation in C++?

    In short: A specialisation provides a different definition of a template for particular arguments: a full specialisation for one exact set, and, for class templates only, partial specialisation for a family such as all pointer types.

    Sometimes the generic version of a template is wrong or slow for one type. A full specialisation, template <> struct Describe<bool> { ... };, replaces the template entirely for that argument, and the compiler uses it whenever that exact argument is named. Class templates also allow partial specialisation, template <typename T> struct Describe<T*>, which covers a whole family of arguments, and the most specialised matching version wins, as below. Function templates cannot be partially specialised, and fully specialising them interacts poorly with overload resolution, so the usual advice for functions is to overload instead. std::vector<bool>, which packs bits instead of storing bools, is the standard library's best-known specialisation, and its oddities are an interview topic of their own.

    template <typename T>
    struct Kind {
        static const char* what() {
            return "value";
        }
    };
    template <typename T>
    struct Kind<T*> {
        static const char* what() {
            return "pointer";
        }
    };
    
    int main() {
        std::cout
            << Kind<int>::what()
            << " "
            << Kind<int*>::what();
    }
    // value pointer
  4. 4.What are non-type template parameters in C++?

    In short: Template parameters that are values rather than types, such as the size in std::array<int, 3>; each distinct value creates a separate instantiation, fixed at compile time.

    A template parameter can be a compile-time constant of an integral or enumeration type, a pointer, and since C++20 a floating-point value or a simple class type. std::array<T, N> is the familiar example: the size is part of the type, so std::array<int, 3> and std::array<int, 4> are different types, the size costs nothing at run time, and it can be used where a constant expression is required, as the static_assert below shows. Since C++17, auto can declare a non-type parameter whose type is deduced from the argument. The values must be known at compile time, which is exactly what makes them useful for sizes, bit widths and fixed configuration, and what rules them out for anything read at run time.

    template <typename T, int N>
    struct Ring {
        T buf[N];
        static constexpr int n() {
            return N;
        }
    };
    
    int main() {
        Ring<char, 8> r;
        static_assert(r.n() == 8);
        std::cout << sizeof(r.buf);
    }
    // 8
  5. 5.What are variadic templates and fold expressions in C++?

    In short: A variadic template takes any number of template arguments through a parameter pack; a C++17 fold expression applies an operator across the whole pack in one expression.

    Declaring template <typename... Ts> creates a parameter pack that can hold zero or more types, and a function parameter pack, Ts... args, holds the matching arguments. Before C++17, packs were consumed recursively, one argument per call, with a base case to end the recursion. A fold expression expands the pack with an operator directly: (args + ...) adds every argument, and (std::cout << ... << args) prints them all, as below. sizeof...(args) gives the number of elements in the pack. Variadic templates are how std::make_unique forwards any constructor arguments, how std::tuple holds any number of types, and how std::format and std::printf-style wrappers are made type-safe.

    template <typename... Ts>
    auto sum(Ts... args) {
        return (args + ...);
    }
    
    int main() {
        std::cout << sum(1, 2, 3)
            << ' '
            << sum(0.5, 1.5);
    }
    // 6 2
  6. 6.What are concepts in C++20, and what problem do they solve?

    In short: Concepts are named compile-time requirements on template arguments; a constrained template rejects an unsuitable type at the call with a short error, instead of failing deep inside its body.

    An unconstrained template accepts any type until some operation in its body fails, and the resulting error points into the template's implementation, often through several layers of the standard library. A concept states the requirements up front: template <std::integral T> or template <typename T> requires std::totally_ordered<T> means the template is only a candidate for types that satisfy them, so a bad call fails at the call site with a message naming the unmet concept, as below. Concepts also take part in overload resolution, so a more constrained overload is preferred over a less constrained one, which replaces most uses of the older SFINAE and std::enable_if techniques. The standard library defines dozens of ready-made concepts in <concepts>.

    template <std::integral T>
    T half(T x) { return x / 2; }
    
    int main() {
        std::cout << half(9);
        half(9.0);
        // does not compile
    }
    // 4
  7. 7.Why is typename needed in front of some names inside templates?

    In short: Inside a template, a name that depends on a template parameter, such as T::value_type, is assumed to be a value, so typename must tell the compiler it names a type.

    When the compiler reads a template, it does not yet know what T will be, so it cannot tell whether T::value_type names a type, a static member or a function. The language resolves the ambiguity by assuming a dependent qualified name is not a type, which means a declaration such as T::value_type x; is parsed as an expression and rejected. Writing typename T::value_type x; states that it is a type. The same issue affects templates nested in dependent names, which need the template keyword, as in t.template get<0>(). C++20 relaxed the rule in contexts that can only be types, such as return types and alias declarations, and using type aliases such as std::ranges::range_value_t<T> avoids it entirely.

    template <typename C>
    typename C::value_type
    first(const C& c) {
        typename C::value_type v =
            *c.begin();
        return v;
    }
    
    int main() {
        std::vector<int> v{7, 8};
        std::cout << first(v);
    }
    // 7

How the diagnostic asks it

One question from the C++ bank, exactly as a sitting would show it. The bank has 3 on templates and 30 across C++.

Templates · mediumCPP-021

What happens when this C++ program is compiled and run?

#include <iostream>

template <typename T>
T maxOf(T a, T b) {
    return a > b ? a : b;
}

int main() {
    std::cout << maxOf(3, 2.5);
}
  1. 1It prints 3
  2. 2It does not compile: T is deduced as int from 3 and as double from 2.5correct
  3. 3It prints 2.5
  4. 4It prints 3.0

Template argument deduction works out T from each argument on its own: 3 says T is int, 2.5 says T is double, and the two disagree, so deduction fails and the call does not compile: g++ reports no matching function for maxOf(int, double), noting deduced conflicting types for parameter 'T'. Deduction never converts arguments to find a common type, which rules out the three answers that print something. The fixes are to name the type, maxOf<double>(3, 2.5), which then prints 3, because cout shows the double 3.0 as 3, or to give the template two type parameters. std::max follows the same rule, so std::max(3, 2.5) fails too.

Measure it

Reading answers tells you what’s true. A diagnostic tells you what you get wrong.

10 C++ questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.

What the readiness test measures · how the score is computed

By Harshit · updated