9

I want a program that defines a macro that can count the number of arguments and pass them to a function sum which sums the arguments' values and returns the total. I managed to do it on GCC but I want to achieve it on Visual C++ 14.

#include "stdafx.h"
#include <iostream>
#include <cstdarg>


#define ELEVENTH_ARGUMENT(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, ...) a11
#define COUNT_ARGUMENTS(...) ELEVENTH_ARGUMENT(dummy, ## __VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)

#define SUM(...) sum(ELEVENTH_ARGUMENT(dummy, ## __VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))


int sum(int n, ...) {

    int sz{ n };
    va_list ap;
    va_start(ap, n);
    int tmp{};
    while (--sz)
        tmp += va_arg(ap, int);

    va_end(ap);

    return tmp;
}

int main() {

    std::cout << COUNT_ARGUMENTS(4,57,22,10,5,6,2,8,68,24,24,86,89,89,96,86) << std::endl; // 1
    std::cout << SUM(5, 57, 4, 5) << std::endl; // 0
    std::cout << COUNT_ARGUMENTS(5, 57, 10) << std::endl;// 1


    std::cout << std::endl;
    std::cin.get();
}

I don't know what's wrong with my code, it always gives me the sum is 0.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
AdamMenz
  • 381
  • 1
  • 13
  • 3
    Shouldn't you forward the numbers passed to `SUM` to the function? And since this is C++, why not a variadic template? – tkausl Feb 01 '19 at 20:53
  • 1
    Variadic macro has limited utility in C++ due to support for variadic templates. The question has more relevance in C, but an [answer already exists](https://stackoverflow.com/q/2124339/315052). – jxh Feb 01 '19 at 21:14
  • Possible duplicate of [C++ preprocessor \_\_VA\_ARGS\_\_ number of arguments](https://stackoverflow.com/questions/2124339/c-preprocessor-va-args-number-of-arguments) – Mikel F Feb 01 '19 at 21:47

2 Answers2

13

Don't use a variadic macro. Visual C++ 14 (or 2015) is a C++11/14 compliant compiler. That means it it supports variadic templates. You can easily recurse a parameter pack to get the sum of the parameters and getting the count can be done by using sizeof.... This lets you write count as

template<typename... Args>
auto count(Args&&...)
{
    return sizeof...(Args);
}

and then sum can be written as

// base case
template<typename T>
auto sum(T&& first)
{
    return first;
}

// multiple parameters case
template<typename T, typename... Args>
auto sum(T&& first, Args&&... rest)
{
    return first + sum(rest...);
}

using those in

int main()
{
    std::cout << count(3,4,5) << "\n";
    std::cout << sum(3,4,5);
}

prints

3
12

which you can see in this live example.


As suggested by HolyBlackCat you can use the dummy array trick to avoid using recursion. That would give you a sum that looks like

template <typename ...P> 
auto sum(const P &... params)
{
    using dummy_array = int[];
    std::common_type_t<P...> ret{}; // common_type_t is to find the appropriate type all of the parameter can be added to
    (void)dummy_array{(void(ret += params), 0)..., 0}; // add the parameter to ret, discard it's result, return the value of 0 for the array element
    return ret;
}

Do note though that this might not work correctly for all types, like shown here with std::valarray. Changing it to

template <typename T, typename ...P> 
auto sum(T first, P&&... rest) // copy the first parameter to use it as the accumulator
{
    using dummy_array = int[];
    (void)dummy_array{(void(first += params), 0)..., 0}; // add the parameter to ret, discard it's result, return the value of 0 for the array element
    return first;
}

should be more correct, although it could probably be improved some more (suggestions/edits welcomed)


If you can use a C++17 complaint compiler then sum can be even further simplified using a fold expression like

template<typename... Args>
auto sum(Args&&... rest)
{
    return (rest + ...);
}
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • As an alternative to recursion, I suggest the good ol' [dummy array trick](https://gcc.godbolt.org/z/8hz820). – HolyBlackCat Feb 01 '19 at 21:33
  • 1
    @HolyBlackCat Good suggestion. I've added it to the answer. I have noted though that it will fail as is if you are using `std::valarray` parameters. – NathanOliver Feb 01 '19 at 21:42
7

Adding onto @NathanOliver, if you'd like to use variadic templates without recursion, std::initializer_list and std::common_type are both available in C++11, allowing you to do this instead:

template <typename... Args,                                      
          typename T = typename std::common_type<Args...>::type> 
T sum(Args&&... args) {                                          
    std::initializer_list<T> l{args...};                         
    return std::accumulate(l.begin(), l.end(), T{});             
}                                                                

Edit: Note that this solution will allow sum to be invoked on all types that are implicitly convertible to a common type (that is what common_type does).

For example:

sum(1, 2, 3)    // Ok, all ints
sum(1, 2, true) // Ok, bool converts to int
sum(1, 2, 3.)   // Error, int to double is a narrowing conversion
Richard
  • 338
  • 2
  • 11