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 (of int).

  • push<5> adds value 5 to the stack.

  • push<6> adds another value to the stack (now it holds 2 values).

  • add pops 2 values from the stack and pushes the result (the stack now holds a single value of 11).

  • value asserts 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 type T.

  • commands starts 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

  • value asserts that the stack only contains a single result and returns the result as a "plain" value (the type should correspond to the starting type T in calc<T>.

  • head is like value but does not assert that there's exactly one single stacked item; it simply returns the top of the stack.

  • stack returns a std::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 | stack can not return constexpr values.)

RPN Commands

  • push<VAL> pushes a value VAL to the top of the stack. The VAL is some type-level literal. The computations in calc<T> should accept all literals that are convertible to T.

  • drop removes a single value from the top of the stack.

  • dup replicates the value on the top of the stack (stack grows by 1 element)

  • add and mult pop 2 values from stack, respectively run operator+ and operator* on these, and push the result

  • peek<N> for some integral value N finds the element in depth N and pushes it to the top of the stack. E.g., peek<0> is exactly equivalent to dup, and calc<char> | push<'a'> | push<'b'> | peek<1> | peek<1> creates a stack that contains 4 letters a 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 int from calc<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 as stack<float, 1.2, 3.4>`. The stack structures should not hold any data.

  • The recommended type for representing the push operation is template<typename T, T Val> struct push_t {};. The T in this template does not necessarily need to be the same as the Item from the stack type above, but it should be convertible to it.

  • Store the commands as 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<>) and template<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.hpp with a complete solution produced by the author is 102 lines of clang-formatted code. 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.)