DEV Community

Coral Kashri
Coral Kashri

Posted on • Originally published at cppsenioreas.wordpress.com on

Into the Extreme – Fold-Expressions

Fold expressions exist in C++ since C++17 and significantly affect how we treat variadic templates. Back in the day, I wrote about fold-expressions as part of the metaprogramming series, but today we will explore the extreme cases of fold-expression usages.

An important disclaimer before we start: In this article, code examples show variadic template usages with arguments passed by value, without forwarding them. This is done to simplify them and to focus on the idea behind the examples.

Empty Variadic Parameters

In the case of a unary fold (fold expression without initialization), this case is legal for 3 types of operators: &&, ||, and ,.

Operator &&


template <typename ...Args>
auto and_cond(Args... args) {
    return (args && ...);
}

Enter fullscreen mode Exit fullscreen mode

In case of empty parameters (for the call and_cond()), the function will return true. A reasonable explanation for this decision might be that && operator requires there won’t be any part that evaluates false. In this case, there are no parts at all, so none of the parts evaluates false, and therefore the result should be true.

Operator ||


template <typename ...Args>
auto or_cond(Args... args) {
    return (args || ...);
}

Enter fullscreen mode Exit fullscreen mode

In case of empty parameters, the function will return false. A reasonable explanation for this decision might be that || operator requires that at least one of the parts will evaluate true. Because there are no parts at all, none of them evaluates true, therefore the result is false.

Operator ,


template <typename ...Args>
auto comma_op(Args... args) {
    return (args , ...);
}

Enter fullscreen mode Exit fullscreen mode

For this case, when there are no parameters passed, the result will be void().

Other operators

For other operators, any call without parameters won’t compile.

Variadic pack with a single parameter

Here things get a little bit unexpected. When passing only one parameter to a variadic pack, the result will ignore the operator and return the same type & parameter sent to the function (Tested on both gcc 13.1.0 & clang 16.0.0). For example:


template <typename ...Args>
auto func(Args ...args)
{
    return (args || ...);
}

int main() {
    using namespace std::string_literals;
    std::cout << func("I am a string"s); // Will print "I am a string"
    return EXIT_SUCCESS;
}

Enter fullscreen mode Exit fullscreen mode

The generated code according to C++ Insights is:


template<>
std::basic_string<char> func<std::basic_string<char>>(std::basic_string<char> __args0)
{
  return std::basic_string<char>(static_cast<std::basic_string<char>&&>(__args0));
}

Enter fullscreen mode Exit fullscreen mode

The same would be for any other operator, including +=, .* and ->.

Binary fold expression without parameters

The same rules for a unary fold expression with a single parameter, are applied to a binary fold expression without parameters:


template <typename ...Args>
auto func(Args ...args)
{
    return (args ->* ... ->* "I am a string"s);
}

int main() {
    std::cout << func(); // Will print "I am a string"
    return EXIT_SUCCESS;
}

Enter fullscreen mode Exit fullscreen mode

C++ Insights generated code:


template<>
std::basic_string<char> func<>()
{
  return std::operator""s("I am a string", 13UL);
}

Enter fullscreen mode Exit fullscreen mode

Conclusion

We have to pay close attention to extreme cases and can never count on basic logic to be on our side (who would believe that we can send a std::string to a fold expression that uses || or && operators?).

This article was inspired by a conversation between Daisy Hollman and me. Thank you Daisy! ❤

An unrelated note: My articles’ publish rate decreased during the last several months. The reason is the preparations for Daisy’s and mine talk in Core C++ (guess which conversation led to this article).

Top comments (0)