Unary fold with empty parameter pack

Viewed 403
#include <iostream>
#include <utility>

template<std::size_t... items>
constexpr std::size_t count()
{
    return std::index_sequence<items...>().size();
}

template<std::size_t... items>
constexpr std::size_t fold_mul()
{
    if( count<items...>() == 0 )
    {
        return 1;
    }
    else
    {
        return (... * items);
    }
}

int main()
{
    std::cout << "Result: " << fold_mul<>() << "\n";
}

This code is expected to output 1 but instead throws the error:

<source>:19:28: error: fold of empty expansion over operator*
19 | return (... * items);

My question is: Why doesn't this work, as the fold_expression is clearly in the else part.

For reference, this implementation works:

template<typename... Args>
constexpr std::size_t fold_mul();

template<std::size_t... j>
requires (count<j...>() > 0)
constexpr std::size_t fold_mul()
{         
    return (j * ...);
}

template<>
constexpr std::size_t fold_mul()
{
    return 1;
}
2 Answers

The problem is, when being specified fold expression with empty expansion, (... * items) is invalid at compile-time; even it won't be evaluated at run-time.

You can use constexpr if (since C++17); then the else-part will be discarded when being specified fold expression with empty expansion.

If the value is true, then statement-false is discarded (if present), otherwise, statement-true is discarded.

template<std::size_t... items>
constexpr std::size_t count()
{
    return std::index_sequence<items...>().size();
}

template<std::size_t... items>
constexpr std::size_t fold_mul()
{
    if constexpr ( count<items...>() == 0 )
    // ^^^^^^^^^
    {
        return 1;
    }
    else
    {
        return (... * items);
    }
}

The only fold-expressions that have a default value for an empty sequence are && (defaults to true), || (defaults to false), and , (defaults to void()).

* doesn't have a default as far as the language is concerned. But in this case, we can use a binary fold instead of a unary fold:

template<std::size_t... items>
constexpr std::size_t fold_mul()
{
    return (1 * ... * items);
}

This works in the empty case (the value is just 1) and in the non-empty case (since 1 is the identity element for multiplication, so doesn't change the value).

Related