C++ Module 9
Templates and concepts interview questions
Interview questions on templates and concepts: short warm-ups, then what each harder question tests, the first step of a good answer and its follow-ups.
Warm-up questions
Short questions with short answers, each answered in full.
-
Fresher
Why does larger(1, 2.5) not compile when larger is declared template <typename T> T larger(T a, T b), and how can you fix the call?
Answered in the lesson C++ function templates and argument deduction
-
Fresher
Why are class templates usually defined entirely in header files?
Answered in the lesson Class templates and argument deduction (CTAD)
-
Fresher
Are RingBuffer<int, 8> and RingBuffer<int, 16> the same type? What follows from your answer?
Answered in the lesson Non-type and template template parameters
-
Fresher
What is the difference between a full and a partial specialisation, and which kinds of templates can have each?
Answered in the lesson C++ template specialisation and overloading
-
Fresher
What do (args + ...), (args && ...) and (args || ...) produce when the pack is empty?
Answered in the lesson Variadic templates and fold expressions
-
Fresher
What is the difference between a requires-clause and a requires-expression?
-
Fresher
What does if constexpr do that a plain if does not?
Answered in the lesson Type traits, if constexpr and reading SFINAE code
-
Fresher to mid
Can a constexpr function use std::vector? What is the rule?
Answered in the lesson Compile-time programming with constexpr
-
Fresher to mid
What does extern template class Matrix<double>; tell the compiler?
Answered in the lesson Templates in practice: build time and binary size
Questions with model answers
What each question tests, the first step of a good answer and the follow-ups to expect.
-
Fresher to mid
A teammate's template <typename T> T larger(T a, T b) returns "apple" for larger("pear", "apple") with Apple clang and "pear" with GCC. Explain what is deduced and compared, then change the code so that this call is either correct or rejected at compile time.
- What it tests
- Deduction with array-to-pointer decay, what > means for two pointers, and how overload resolution between a template and a non-template can make a template safe.
- First step
- Write down what T becomes. The literals are const char[5] and const char[6]; a by-value parameter makes both decay to const char*, so > compares two addresses, which the standard leaves unordered for unrelated objects. The result depends on where each compiler stores the literals, not on the words.
- Follow-ups
-
- Does switching the parameters to const T& fix it?
- Why does an added overload larger(std::string_view, std::string_view) not change the result?
- Is the original call undefined behaviour?
From the lesson C++ function templates and argument deduction
Model answer, Pro. Model answers open with any pass; prices are in the notice on this page.Opening the model answers with your pass…
Model answers open with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so the model answers can’t be opened at the moment. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
After a teammate adds one call to a function template from a utility header, the build fails with hundreds of lines that point deep inside the header instead of at their code. How do you find the real cause, and what can the template's author do so that the next mistake gives a short error?
- What it tests
- Reading instantiation chains on two compilers, telling the failing operation from the call that caused it, and making a template's requirements explicit with constraints.
- First step
- Read the first error for the operation that failed, for example a < on two Point objects, then follow the "required from" notes of GCC or the "in instantiation of … requested here" notes of Clang down to the last frame in a file you own. That call, and the type it passes, is what to change.
- Follow-ups
-
- Why did the header compile without errors before the new call?
- Does a static_assert inside the template shorten the error as much as a constraint?
- The chain runs through three library templates. Which frame do you fix?
From the lesson C++ function templates and argument deduction
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
To cut build times, a colleague moved the members of Matrix<T> into matrix.cpp and added template class Matrix<double>; and template class Matrix<std::string>;. The std::string line does not compile, although Matrix<std::string> worked before, and another file now fails to link with Matrix<float>. Explain both failures and choose a better layout.
- What it tests
- Implicit against explicit instantiation of class template members, where instantiation happens, extern template, and the trade-off between build time and the set of types a template supports.
- First step
- Name the rule behind each failure. Implicit instantiation compiles only the members a program calls, while an explicit instantiation definition compiles every member, so one member that cannot work for std::string now fails. And no file can instantiate Matrix<float> any more: the users see only declarations, and matrix.cpp lists only two types.
- Follow-ups
-
- How do you keep the explicit instantiation for strings if one member cannot work for them?
- How does extern template differ from an explicit instantiation definition?
- How would you check that the change really made the build faster?
From the lesson Class templates and argument deduction (CTAD)
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Reading a colleague's change, you find auto copies = std::vector{points.begin(), points.end()}; and std::optional retry{limit};, where limit is already a std::optional<int>. What does each line deduce, which one is a bug, and what rule would you set for class template argument deduction in your team?
- What it tests
- The braced-list and copy-deduction rules of CTAD, how to check a deduced type, and a team rule that keeps CTAD where it helps and out of places where it misleads.
- First step
- Work out the types before judging. Braces make the two iterators the elements of an initializer list, so the first line deduces a vector of iterators, not of points. The second line has one argument that is already a std::optional, so the copy deduction candidate wins and retry is a std::optional<int>, a copy of limit.
- Follow-ups
-
- How do you prove what a declaration deduced, on both compilers?
- When does a class need a deduction guide, and how can its author say that CTAD is intended?
- Should CTAD be allowed in a public API, such as a data member or a return type?
From the lesson Class templates and argument deduction (CTAD)
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
The capacity of a message queue declared RingBuffer<Message, N> must now come from a configuration file read at start-up. Why can't the value from the file be N, and how would you redesign the buffer so that both compile-time and run-time capacities work without writing the logic twice?
- What it tests
- What a constant expression is, what a compile-time size buys, and designing one implementation that serves a fixed and a dynamic capacity, as std::span does with its extent.
- First step
- N is part of the type, and types are fixed when the program is compiled; a number read at start-up does not exist yet. So first say what the compile-time capacity was buying (storage inside the object, no allocation, a size the optimiser knows) and decide which of those the new requirement can give up.
- Follow-ups
-
- Could you instantiate the buffer for every capacity the file may contain and pick one at run time?
- How does std::span express a fixed or a dynamic size with one template?
- What does a run-time capacity cost compared with a fixed one?
From the lesson Non-type and template template parameters
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
You want Field<"price", double> so that each field's name is part of its type. Why does template <const char* Name> refuse "price", how does a C++20 FixedString parameter make it work, and what does putting names into types cost?
- What it tests
- The rules for pointer and class-type template arguments, structural types and deduction of FixedString's length, and the compile-time and binary-size costs of values in types.
- First step
- State the rule first: a template argument for a pointer parameter may not point to a string literal object, so the literal is rejected however it is written. A class-type parameter takes the literal as a constructor argument instead, and the class keeps the characters in a public array.
- Follow-ups
-
- What makes a class usable as a template parameter type?
- How does the compiler know N in FixedString<N> when you write Field<"price", double>?
- Where do the names end up in the compiled program?
From the lesson Non-type and template template parameters
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A logging header declares template <typename T> void log_value(T), then an explicit specialisation template <> void log_value(int*), then template <typename T> void log_value(T*). Calls with an int* never reach the specialisation. Explain what the compiler does, and restructure the code so that it no longer depends on the order of declarations.
- What it tests
- How overload resolution, partial ordering and explicit specialisation interact, and replacing function template specialisation with overloads or with delegation to a class template.
- First step
- Separate the two steps. Overload resolution chooses among primary templates and ordinary functions only: both templates accept an int*, and log_value(T*) is more specialised, so it wins. The specialisation was attached to log_value(T) when it was declared, before the pointer overload existed, so it belongs to the loser.
- Follow-ups
-
- What changes if the specialisation is moved below the pointer overload?
- How do you get the effect of a partial specialisation for a function?
- Do specialisations of class templates have the same problem?
From the lesson C++ template specialisation and overloading
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
You need std::unordered_map<GridPoint, int> for a 500 × 500 grid. Show how you make GridPoint usable as a key, what the standard allows you to add to namespace std for it, and how you would check that your hash spreads the keys well.
- What it tests
- The requirements of unordered containers on keys, the rules for specialising standard library templates, and measuring hash quality with bucket statistics instead of guessing.
- First step
- An unordered map needs two things for its key: equality, which a defaulted operator== gives, and a hash, which it finds as std::hash<GridPoint> unless you pass a hasher type. Specialising std::hash for your own type is allowed; the hash must give equal values for equal points.
- Follow-ups
-
- Is row + col a good hash for grid points? What would you measure?
- Could you specialise std::hash<std::pair<int, int>> instead and use pairs as keys?
- When would you pass a hasher as the third template argument instead of specialising std::hash?
From the lesson C++ template specialisation and overloading
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A helper run_all(fs...) runs several callables with report(fs()...), passing their results to report. After a compiler upgrade, a colleague sees the callables run in a different order. Can that happen? Rewrite run_all so that the callables always run from left to right and report still receives every result.
- What it tests
- The sequencing rules of function arguments, comma folds and braced lists, and keeping results of different types in order without relying on the compiler.
- First step
- Yes. In report(fs()...), each fs() initialises one parameter of report, and the standard says those initialisations are indeterminately sequenced: any order is allowed and need not stay the same between compilers, targets or optimisation levels. The original code depended on one compiler's habit.
- Follow-ups
-
- How do you keep the results when the callables return different types?
- Would a comma fold be enough here?
- What if some callables return void?
From the lesson Variadic templates and fold expressions
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
Compare a print_all written with C++11 recursion over a pack and one written with a C++17 fold expression: what each instantiates for a call with five arguments, how each handles no arguments, and what each does to build times and error messages. Which do you choose, and when is recursion still the right tool?
- What it tests
- How pack recursion instantiates one function per step, compiler depth limits, empty-pack behaviour, and when a fold, an expansion or pack indexing replaces recursion.
- First step
- Count instantiations. The recursive version peels one argument per call, so five arguments make five instantiations of different lengths plus a non-template base case; the fold makes one instantiation with all five types. nm on the object file shows six functions against one.
- Follow-ups
-
- What happens with 1,000 arguments?
- How does each version behave with an empty pack?
- Give a case that still needs recursion, or a different tool than a fold.
From the lesson Variadic templates and fold expressions
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A library has encode(T) constrained by requires std::is_integral_v<T>. A colleague adds a faster overload for unsigned types, constrained by requires std::is_integral_v<T> && std::is_unsigned_v<T>, and now every call with an unsigned int fails as ambiguous. Explain why, and fix it without changing any caller.
- What it tests
- How constrained overloads are ordered, why subsumption sees only constraints that come from the same concept, and designing a small family of concepts.
- First step
- Both overloads are viable for unsigned int and have identical parameter types, so only their constraints can decide. The compiler prefers an overload whose constraints subsume the other's, and it compares atomic constraints by where they were written, not by their text. The two appearances of std::is_integral_v<T> are different atomic constraints, so neither overload subsumes the other.
- Follow-ups
-
- Why does the compiler not compare the text of the two expressions?
- Would switching only the new overload to std::unsigned_integral fix it?
- How would a test prove that the fast path is the one chosen?
From the lesson C++20 concepts and requires-clauses
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
You own a generic median(std::vector<T>) used across a large codebase, and reviewers ask you to constrain it. Would you use std::totally_ordered, std::sortable or a concept of your own? Say what each choice costs the callers.
- What it tests
- Stating a template's real requirement, satisfying against modelling a concept, and what a constraint does to callers, overloads and error messages.
- First step
- List what the body needs: it copies the values, orders them and reads the middle element, so it needs copyable elements whose < is a strict weak ordering. Then compare the candidates against that list: std::totally_ordered asks for all six comparisons, std::sortable with its default comparator does too, and a concept of your own can ask for exactly <. None of them can check that the ordering is sound.
- Follow-ups
-
- A caller's type has only operator<. What happens with each choice?
- Should median accept any range instead of a std::vector?
- What do you do about NaN in a std::vector<double>?
From the lesson C++20 concepts and requires-clauses
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A C++17 library defines two serialize(T) overloads: one with typename = std::enable_if_t<std::is_integral_v<T>>, one with the same form for floating-point types. The build fails with "redefinition of serialize". Explain the cause, give the C++17 fix, and say what C++20 changes.
- What it tests
- How SFINAE removes candidates, what belongs to a function template's signature, and moving an overload set from enable_if to concepts.
- First step
- Default template arguments are not part of a function template's signature. Both declarations are template <typename T, typename> std::string serialize(T), so the second defines the same template again, and enable_if never gets a chance to remove anything. The condition has to become part of the signature.
- Follow-ups
-
- Why does enable_if in the return type not have this problem?
- What happens when a type satisfies both conditions after the fix?
- How do you move the overload set to C++20 without breaking C++17 users?
From the lesson Type traits, if constexpr and reading SFINAE code
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Fresher to mid
A to_json(value) template tests std::is_integral_v<T>, then std::is_floating_point_v<T>, then strings, with if constexpr. In production, true is written as 1, char fields appear as numbers, and a const int& member reaches the static_assert(false) at the end. Explain each bug and fix the chain.
- What it tests
- How the standard classifies bool, char and references, the order of if constexpr branches, and testing a type dispatch.
- First step
- Each symptom is a classification question. bool and char are integral types, so they take the integer branch, and a reference type belongs to no arithmetic category at all, so const int& falls through every test to the static_assert. Test std::remove_cvref_t<T>, and order the branches from the most specific type to the most general.
- Follow-ups
-
- Should a char be written as a number or as a one-character string?
- How would you test the dispatch for every type the library supports?
- Would overloads constrained by concepts be better than one if constexpr chain?
From the lesson Type traits, if constexpr and reading SFINAE code
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Mid to senior
A colleague moved a 65,536-entry lookup table into a constexpr function in a header. Clang now stops with "constexpr evaluation hit maximum step limit", and with the GCC limit raised the build is minutes slower. The header is included by 300 source files. What are your options, and which do you choose?
- What it tests
- What constant evaluation costs and who pays for it, the compilers' limits and flags, and the alternatives: generated sources, data files and computing at start-up.
- First step
- Measure one translation unit that includes the header, with CPU time and peak memory for each compiler, and multiply by the 300 files, because every translation unit that evaluates the table evaluates it again. Then compare that cost with options that compute the table once per build or once per run.
- Follow-ups
-
- Is raising -fconstexpr-steps and -fconstexpr-ops-limit acceptable?
- How do you keep the table checked at compile time once it leaves the header?
- What changes if the table must sit in flash memory on a microcontroller?
From the lesson Compile-time programming with constexpr
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
After a refactor, static_assert(crc32("123456789") == 0xCBF43926) fails, so the program no longer builds. Nothing can be printed during constant evaluation. How do you find the bug?
- What it tests
- Debugging code that runs in the compiler: reading what the compilers report, running the same function at run time, checking intermediate values, and knowing the CRC's parameters.
- First step
- Read the values first: for a failed comparison, Clang's note shows both sides ("expression evaluates to") and GCC 16 shows what the comparison reduces to, so you see the wrong checksum itself. Then use the fact that a constexpr function is also an ordinary function: call it at run time in a small test and print the table and the steps.
- Follow-ups
-
- The value is FC4F2BE9 instead of CBF43926. What does that suggest?
- Which inputs would you add as permanent tests?
- How can a static_assert report the wrong value in its own message?
From the lesson Compile-time programming with constexpr
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes
-
Senior to staff
A service takes 14 minutes to build. Clang's -ftime-trace shows that in 300 source files about 40 % of the front-end time goes to instantiating one JSON library's templates. Plan the fix: what you measure, what you change first, and how you prove it helped.
- What it tests
- Reading build traces across a whole build, the per-file cost of instantiation, and choosing between extern template, hoisting, header hygiene, precompiled headers and modules.
- First step
- Measure the whole build, not one file: collect the traces of all 300 files, add up the time per header and per template, and rank them. Then find the few specialisations that most files instantiate, such as the parser and writer for one string type, because those are what extern template or a precompiled header can remove.
- Follow-ups
-
- Would precompiled headers or C++20 modules help more than extern template here?
- How do you stop the build time from creeping back?
- When would you split the header instead?
From the lesson Templates in practice: build time and binary size
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 4 minutes
-
Mid to senior
After you add extern template class Matrix<double>; to matrix.h and template class Matrix<double>; to matrix.cpp, debug builds shrink, but a new test program fails to link with an undefined Matrix<double>::multiply, and a benchmark built with -O2 got slower. Explain both.
- What it tests
- What an explicit instantiation declaration promises, how build targets must link the instantiating file, and how extern template interacts with inlining.
- First step
- The declaration is a promise that some object file of the program defines Matrix<double>'s members. The test program compiles files that include matrix.h but does not link matrix.o, or the library that contains it, so nothing keeps the promise and the linker reports the undefined symbol.
- Follow-ups
-
- How do you fix the build so this cannot happen again?
- Why did the -O2 benchmark get slower, and how would you confirm it?
- When would you keep extern template for debug builds only?
From the lesson Templates in practice: build time and binary size
Model answer, Pro.In the answer:
- Model answer
- Follow-up answers
- Common mistakes
- Rubric
- Variants
- about 3 minutes