简体   繁体   中英

C++ variadic template sum

I am trying to understand the C++11 feature called "variadic". Look at this simple code:

#include <iostream>

using namespace std;

template<typename T, typename... Args>
T adder(T first, Args... args) {
  return first + adder(args...);
}

int main() {

  int c = adder(1,8,4);
  cout << c << endl;

  cout << "Hello World!" << endl;
  return 0;

}

Readig the c++ primer book I have understood that they work in recursive way and I also see that in the recursive call the args... part is passed.

I am using MinGW 5 and QtCreator to test this. Look

在此输入图像描述

Ok, to fix the Too few arguments I could call adder(first, args...) but now the recursion is not proper and the program crashes. What to do? I cannot understand how to do this.


Looking online I've found an example like this

template <typename T, typename... Rest>
double sum(T t, Rest... rest) {
  return t + sum(rest...);
}

It's basically the same. Do I have to put an explicit (non template) return type?

You need the "stop-recursion-case" (do not know the correct name now; UPDATE: it's called "base-case", thanks Quentin) with just one argument when the template function is unfolding.

#include <iostream>

template<typename T>
T adder(T first) {
  return first;
}

template<typename T, typename... Args>
T adder(T first, Args... args) {
  return first + adder(args...);
}

int main() {
  const int c = adder(1, 8, 4);
  std::cout << c << '\n';
  return 0;
}

Your recursion unfolds like this:

adder(1,8,4)
-> adder<int,int,int>(1,8,4)
-> 1 + adder<int,int>(8,4)
-> 1 + 8 + adder<int>(4)
-> 1 + 8 + 4 + adder<>()

so Args... is getting shorter every time, and eventually is empty. But your declaration

template<typename T, typename... Args>
T adder(T first, Args... args);

can't be called with zero arguments, it always needs at least one ( first ).

So, the options are either

  1. add an overload which does take zero arguments, like

     int adder() { return 0; } 
  2. add an overload taking exactly one argument, which doesn't try to continue the recursion:

     template <typename T> T adder(T t) { return t; } 

either one will fix the bug, but the second is much better, because it works for any T , and the first will only add things that are implicitly convertible from int{0} .

This pattern - the general recursive case plus the terminal case which stops recursion - was common before variadic templates were introduced (we previously used LISP-like recursive lists for this sort of thing, which naturally use recursion like this).

The newer style enabled by variadic templates takes advantage of pack expansion to avoid recursion entirely. See for example the summation example using std::apply

The answer from @christian-g is correct. However, I would like you to notice that this function can be generalised to any return type with auto keyword.

template<typename T1, typename T2>
auto adder(const T1& t1, const T2& t2) {
    return t1 + t2;
}

template<typename T1, typename... T2>
auto adder(const T1& t1, const T2&... t2) {
    return t1 + adder(t2...);
}

Now you can use it for different types

auto sum1 = adder(1, 2, 3); // sum1 is int
auto sum2 = adder(1, 2., 3); // sum2 is double

There was an issue I have run against once with concatenating values to the string result, but it was solved in type deduction from char array to std::string

With C++17 fold expression you can do it with a single function.
You don't need the "stop-recursion-case".

template<typename... Args>
auto sum(Args... args)
{
    return (args + ...);
}

Example usage, all print 42

std::cout << sum(42) << '\n';
std::cout << sum(11, 31.0) << '\n'; // different types
std::cout << sum(3, 4, 5, 6, 7, 8, 9) << '\n';

using namespace std::string_literals;
std::cout << sum("4"s, "2"s) << '\n'; // concatenating strings

A variadic template parameter like Args allows the case with sizeof...(Args)=0 , which returns the number of variadic template arguments. Using C++11 you need to define a case that is valid for no variadic templates given like

template<typename _T>
_T adder(_T first) { return first; }

However considering different types like double and int it is not always meaningful to return _T, mayber you want to change it to auto or declytpe .

Additionally this problem is solved in C++17 if you are intreseted.

template<typename ...Args>
int sum(Args&&... args) {
    return (args + ... + (1 * 2));
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM