Zkouška C++ 2. 6. 2026
RPN Calculator with a Type Twist
Your task is to make a simple reverse-Polish-notation (also known as postfix) calculator.
Contrary to the usual assignment of an RPN calculator, the calculation here must be done entirely at compile time, using types and templates: The input is not coming from the user, but from a programmer who is able to specify computations such as:
constexpr auto x = calc<int> | push<5> | push<6> | add | value;
We may expect that after processing the code above, the compiler deduces that x is equal to 11 (of type int).
The operations of the RPN calculation in the above snippet carry the following semantics:
calc<int>starts a new empty stack (ofint).push<5>adds value5to the stack.push<6>adds another value to the stack (now it holds2values).addpops2values from the stack and pushes the result (the stack now holds a single value of11).valueasserts that the stack contains only a single result, and returns it.
To make matters more entertaining, the programmer will be allowed to store "RPN procedures" as follows:
constexpr auto twice = commands | push<2> | mult; constexpr auto thrice = commands | dup | dup | add | add;
Such stored procedures are used as "shortcuts" for the command list that they save:
constexpr auto x = calc<float> | push<12.34> | twice | thrice | value;
The above code should deduce constant x equal to 74.04f.
The complete list of functionality that you have to implement to pass all tests is listed in the sections below.
Note that your implementation should refuse to compile with certain operations; typically this should happen in case of stack underflow, and when using value on a stack that doesn't contain a unique single result. The tests that are expected to fail (i.e., to not compile) are marked as negative, the usual ones are marked as positive.
Starting Commands
calc<T>starts a new stack of values with typeT.commandsstarts recording a queue of commands so that they can later be applied to a computation. Since the commands do not work with any values yet, the type is left unspecified.
Finishing Commands
valueasserts that the stack only contains a single result and returns the result as a "plain" value (the type should correspond to the starting typeTincalc<T>.headis likevaluebut does not assert that there's exactly one single stacked item; it simply returns the top of the stack.stackreturns astd::vector<T>that contains all values that are currently stacked. The "top of stack" is at the end (higher indexes) of the vector. (Note that computations that end with| stackcan not returnconstexprvalues.)
RPN Commands
push<VAL>pushes a valueVALto the top of the stack. TheVALis some type-level literal. The computations incalc<T>should accept all literals that are convertible toT.dropremoves a single value from the top of the stack.dupreplicates the value on the top of the stack (stack grows by 1 element)addandmultpop 2 values from stack, respectively runoperator+andoperator*on these, and push the resultpeek<N>for some integral valueNfinds the element in depthNand pushes it to the top of the stack. E.g.,peek<0>is exactly equivalent todup, andcalc<char> | push<'a'> | push<'b'> | peek<1> | peek<1>creates a stack that contains 4 lettersa b a b.
Submission
Submit a single file rpnc.hpp that exports the required functionality in the global namespace.
Hints
(The list below contains recommendations; other solution approaches are viable as well.)
Store the stack contents as arguments in the template. You will need to mark your stack type with the type of stack items (i.e., the
intfromcalc<int>), so that this type can be found and used by the operations later.We recommend using a stack type like ;template<typename Item, Item... Stack> struct somestack { ... };
, with instances such asstack<float, 1.2, 3.4>`. The stack structures should not hold any data.The recommended type for representing the
pushoperation istemplate<typename T, T Val> struct push_t {};. TheTin this template does not necessarily need to be the same as theItemfrom the stack type above, but it should be convertible to it.Store the
commandsas a simple type-level list of types (again, as a variadic template). To simplify "moving" of the types from the store towards the computation, make sure that all your RPN operator representations are default-constructible and carry no data (i.e., you can fully reconstruct the operation "object" just from looking at its type).To avoid a common source of problems when implementing compile-time calculation, ensure that choice of templates is not ambiguous. In particular, when folding the template argument list, recall that functions with headers
auto f(SomeType<>)andtemplate<typename... T> auto f(SomeType<T...>)may be both selected to handle the "empty case". Use an explicit "head" of the list (typename T0) to allow the compiler to differentiate between the cases.In case of any issues or corner cases that are not commented upon in the assignment (typically, insufficient items on stack to run the operation), the program should simply not compile.
A
rpnc.hppwith a complete solution produced by the author is 102 lines ofclang-formattedcode. If you see the size of your solution outgrowing that expectation (e.g., your work-in-progress solution is already 500 lines of code), you might want to reiterate on your design choices.(The above is only a recommendation that will hopefully prevent you from struggling too far in a possibly wrong direction; code length has no direct impact on evaluation.)