Why do I need constexpr in recursion with parameter packs

91 Views Asked by At

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.

2

There are 2 best solutions below

0
On BEST ANSWER

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.

0
On

Also you don't have to use explicit recursion, from C++17 onward you can use a fold expression.

The following code will output 1, 2, hello

#include <iostream>

template<typename First, typename... Args>
void printArgs(First&& first, Args&&... rest) 
{
    std::cout << std::forward<First>(first);

    //https://en.cppreference.com/w/cpp/language/fold
    // the part before ,... will be repeated/expanded
    // each time with one argument
    ((std::cout << ", " << std::forward<Args>(rest)),...);
}

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

    return 0;
}