I really like the answers by @Brian and @Passer By; it has the same semantics as just expanding the parameter pack with the operator. I'd like to add a (contrived) example to demonstrate what that means.
Let's modify your sum function first to use decltype(auto) and forwarding semantics:
template<typename... Args>
decltype(auto) sum(Args&&... args) {
decltype(auto) fold = (... + std::forward<Args>(args));
return fold;
}
Still functionally the same, only now sum will return exactly the type that fold is, and fold will take on the exact type of the summation.
Our fold expression is called a unary left fold and its expansion will look like this:
((E1 op E2) op ...) op EN
More concretely, assume that Args can be treated like an array of length N:
((Args[0] + Args[1]) + ...) + Args[N-1]
Which is basically the same as
Args[0] + Args[1] + ... + Args[N-1]
So now that we've taken some mysticism out of the whole thing it doesn't take much to see that the type of the expression is really just the type of the result of adding up whatever types come across on both sides of the + operator.
I can contrive the following type Foo that has some strange addition semantics:
const std::string global = "Global string";
struct Foo{
Foo(int){}
};
const std::string& operator +(const Foo&, const Foo&){
return global;
}
const std::string& operator +(const std::string& _str, const Foo&){
return _str;
}
And now when I call sum for instances of Foo, I'll receive a const std::string& in return, which is definitely not a prvalue:
int main() {
std::cout << sum(Foo{10}, Foo{2}, Foo{2}) << std::endl;
}
Prints
Global string