Search code examples
c++templatesif-constexpr

Why do I need constexpr in recursion with parameter packs


I have a simple recursive function to print each argument from a parameter pack

#include <iostream>

template<typename T, typename... Args>
void printArgs(T first, Args... rest) {
    std::cout << first << " ";
    
    if constexpr (sizeof...(rest) > 0) { // doesn't compile without constexpr
        printArgs(rest...);
    } else {
        return;
    }
}

int main() {
    printArgs(1, 2, "hello");

    return 0;
}

Q1: Why do I need constexpr in the if for the program to compile?
Q2: Shouldn't the condition be sizeof...(rest) > 1? Because if the size is 1 and I call printArgs again, then wouldn't rest be empty? (is it ok for it to be empty?)

I saw similar questions, like "constexpr if" vs "if" with optimizations - why is "constexpr" needed?, but I don't see how those answers relate to my case.


Solution

  • I'll start with the second Q, because the answer to that also explains Q1.

    Q2: Shouldn't the condition be sizeof...(rest) > 1? Because if the size is 1 and I call printArgs again, then wouldn't rest be empty? (is it ok for it to be empty?)

    Your mistake is to count T for sizeof...(rest). But if you call printArgs(onlyOne) then T is deduced from onlyOne and Args is empty, and sizeof...(rest) is 0. Your function can be called with 1 (T first) plus zero or more, (Args... rest), arguments.

    Q1: Why do I need constexpr in the if for the program to compile?

    If you remove the constexpr you get the following error:

    <source>: In instantiation of 'void printArgs(T, Args ...) [with T = const char*; Args = {}]':
    <source>:8:18:   recursively required from 'void printArgs(T, Args ...) [with T = int; Args = {const char*}]'
    <source>:8:18:   required from 'void printArgs(T, Args ...) [with T = int; Args = {int, const char*}]'
    <source>:15:14:   required from here
    <source>:8:18: error: no matching function for call to 'printArgs()'
        8 |         printArgs(rest...);
          |         ~~~~~~~~~^~~~~~~~~
    <source>:4:6: note: candidate: 'template<class T, class ... Args> void printArgs(T, Args ...)'
        4 | void printArgs(T first, Args... rest) {
          |      ^~~~~~~~~
    <source>:4:6: note:   template argument deduction/substitution failed:
    <source>:8:18: note:   candidate expects at least 1 argument, 0 provided
        8 |         printArgs(rest...);
          |         ~~~~~~~~~^~~~~~~~~
    

    Because on the last recursion only 1 argument is passed to printArgs and rest has no elements. In that case printArgs(rest...) fails to compile because (see above) your function can only be called with 1 or more. if constexpr results in the false branch being discarded and the part where you call printArgs() is never instantiated.