Menu
CoddyTech

C++ Templates Interview Questions

Function and class templates, specialization, SFINAE, concepts and variadic templates.

10 questions
  1. What are templates in C++?

    A template is a blueprint the compiler uses to generate a separate, fully typed function or class for each type you use it with, at compile time.

    C++
    #include <iostream>
    #include <string>
    using namespace std;
    
    template <typename T>
    T maxOf(T a, T b) {
        return a > b ? a : b;
    }
    
    int main() {
        cout << maxOf(3, 7) << " " << maxOf(2.5, 1.5) << " "
             << maxOf<string>("pear", "apple") << "\n";
    }

    This prints 7 2.5 pear. Without the explicit <string>, the arguments would be two const char* and > would compare addresses. See the templates docs.

  2. How do you write a class template? Give an example.

    Put template <typename T> before the class and use T wherever the type varies; each Stack<int> or Stack<std::string> is a separate class.

    C++
    #include <iostream>
    #include <stdexcept>
    #include <string>
    #include <vector>
    using namespace std;
    
    template <typename T>
    class Stack {
        vector<T> items;
    public:
        void push(const T& x) { items.push_back(x); }
        T pop() {
            if (items.empty()) throw out_of_range("empty stack");
            T top = items.back();
            items.pop_back();
            return top;
        }
        bool empty() const { return items.empty(); }
    };
    
    int main() {
        Stack<string> s;
        s.push("a");
        s.push("b");
        cout << s.pop() << s.pop() << " " << s.empty() << "\n";
    }

    This prints ba 1 (since C++17 the two s.pop() calls in one << chain run left to right; see the evaluation order rules). Members are instantiated only when used.

  3. Which template does the compiler pick for TypeName<int>, <double*> and <char>?

    C++
    #include <iostream>
    using namespace std;
    
    template <typename T>
    struct TypeName { static const char* get() { return "unknown"; } };
    
    template <>
    struct TypeName<int> { static const char* get() { return "int"; } };
    
    template <typename T>
    struct TypeName<T*> { static const char* get() { return "pointer"; } };
    
    int main() {
        cout << TypeName<int>::get() << " "
             << TypeName<double*>::get() << " "
             << TypeName<char>::get() << "\n";
    }

    It prints int pointer unknown. The compiler picks the most specialized match: int uses the full specialization, double* the partial specialization for T*, and char the primary template.

    Only class and variable templates can be partially specialized; function templates are overloaded instead. Type traits such as std::is_pointer are built this way.

  4. Why are templates usually defined in header files?

    The compiler needs the full definition where the template is used, to generate code for that type. Defined only in stack.cpp, Stack<int>::push is never generated and the linker reports an undefined reference. Explicit instantiations in the .cpp are the alternative, limiting users to those types.

    // stack.cpp
    template class Stack<int>;
    template class Stack<std::string>;
    
  5. What is SFINAE, and how do C++20 concepts replace it?

    SFINAE ("substitution failure is not an error"): if substituting template arguments into a signature produces invalid code, that template is dropped from overload resolution instead of causing an error. std::enable_if uses it to switch overloads by type.

    C++
    #include <iostream>
    #include <type_traits>
    
    template <typename T>
    std::enable_if_t<std::is_integral_v<T>, const char*> kind(T) { return "integer"; }
    
    template <typename T>
    std::enable_if_t<std::is_floating_point_v<T>, const char*> kind(T) { return "floating"; }
    
    int main() {
        std::cout << kind(5) << " " << kind(2.0) << "\n";
    }

    This prints integer floating. C++20 concepts say the same directly, with readable errors.

    template <std::integral T>
    const char* kind(T) { return "integer"; }
    
    template <std::floating_point T>
    const char* kind(T) { return "floating"; }
    
  6. What are variadic templates and fold expressions?

    A variadic template takes any number of arguments through a parameter pack (typename... Args). C++17 fold expressions apply an operator across the pack without recursion.

    C++
    #include <iostream>
    
    template <typename... Args>
    auto sum(Args... args) {
        return (args + ...);
    }
    
    template <typename... Args>
    void printAll(const Args&... args) {
        ((std::cout << args << " "), ...);
        std::cout << "\n";
    }
    
    int main() {
        std::cout << sum(1, 2, 3, 4) << "\n";
        printAll(1, "two", 3.0);
    }

    This prints 10, then 1 two 3 (3.0 prints as 3). sizeof...(args) gives the pack's size. std::make_unique and emplace_back use variadic templates with perfect forwarding.

  7. What is the difference between templates and macros?

    Macros are text substitution by the preprocessor; templates are part of the language and are type checked by the compiler. Macros also ignore scope and may evaluate an argument twice.

    #define MAX(a, b) ((a) > (b) ? (a) : (b))
    int i = 5;
    int m = MAX(i++, 3);   // i++ runs twice: i ends at 7
    
    template <typename T>
    T max2(T a, T b) { return a > b ? a : b; }
    

    Use templates or constexpr functions for code, and keep macros for include guards, conditional compilation and __FILE__/__LINE__ in logging.

  8. What is the difference between typename and class in a template?

    In a template parameter list there is none: template <class T> and template <typename T> mean the same thing. typename has a second job, though: inside a template it tells the compiler that a name depending on a template parameter is a type.

    C++
    #include <iostream>
    #include <vector>
    using namespace std;
    
    template <class Container>
    typename Container::value_type first(const Container& c) {
        typename Container::const_iterator it = c.begin();   // dependent type: typename required
        return *it;
    }
    
    int main() {
        vector<int> v = {4, 8, 15};
        cout << first(v) << "\n";
    }

    It prints 4. Without typename, Container::const_iterator is parsed as a value and the code does not compile. C++20 drops the requirement in some places where only a type can appear, such as a function's return type.

  9. What are non-type template parameters?

    A template parameter can be a compile-time value instead of a type: an integer, an enum, a pointer or a reference, plus floating-point values and some class types since C++20. std::array<int, 3> uses one for its size.

    C++
    #include <array>
    #include <cstddef>
    #include <iostream>
    using namespace std;
    
    template <typename T, size_t N>
    size_t length(const T (&)[N]) { return N; }   // N is deduced from the array type
    
    template <int Factor>
    int scale(int x) { return x * Factor; }
    
    int main() {
        int scores[] = {90, 75, 88, 62};
        array<int, 3> fixed = {1, 2, 3};
        cout << length(scores) << " " << fixed.size() << " " << scale<10>(3) << "\n";
    }

    It prints 4 3 30. The value must be a constant expression, and each distinct value is a separate instantiation: std::array<int, 3> and std::array<int, 4> are different types. Taking an array as const T (&)[N] is the classic way to get its length; C++17 also allows template <auto V>.

  10. What is CRTP (the curiously recurring template pattern)?

    A class derives from a base template instantiated with itself (struct Square : Shape<Square>). The base casts this to the derived type and calls its functions, which gives polymorphism resolved at compile time: no vtable, and the calls can inline.

    C++
    #include <iostream>
    using namespace std;
    
    template <typename Derived>
    struct Shape {
        double area() const { return static_cast<const Derived*>(this)->areaImpl(); }
        void describe() const { cout << "area " << area() << "\n"; }
    };
    
    struct Square : Shape<Square> {
        double side;
        explicit Square(double s) : side(s) {}
        double areaImpl() const { return side * side; }
    };
    
    struct Rect : Shape<Rect> {
        double w, h;
        Rect(double w, double h) : w(w), h(h) {}
        double areaImpl() const { return w * h; }
    };
    
    template <typename T>
    void print(const Shape<T>& s) { s.describe(); }
    
    int main() {
        print(Square(3));
        print(Rect(2, 5));
    }

    It prints area 9 and area 10. The cost: Shape<Square> and Shape<Rect> are unrelated types, so they cannot share one container. The standard library uses the pattern in std::enable_shared_from_this, and C++23's explicit object parameter (deducing this) replaces many uses.

Coddy programming languages illustration

Learn to code with Coddy

Get started