Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to sum variadic arguments passed in to a variadic macro?

Tags:

c++

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.

like image 645
AdamMenz Avatar asked Feb 01 '19 20:02

AdamMenz


People also ask

How do you use variadic arguments?

It takes one fixed argument and then any number of arguments can be passed. The variadic function consists of at least one fixed variable and then an ellipsis(…) as the last parameter. This enables access to variadic function arguments. *argN* is the last fixed argument in the variadic function.

What is __ Va_args __ C++?

macro expansion possible specifying __VA_ARGS__ The '...' in the parameter list represents the variadic data when the macro is invoked and the __VA_ARGS__ in the expansion represents the variadic data in the expansion of the macro. Variadic data is of the form of 1 or more preprocessor tokens separated by commas.

What is __ Va_opt __?

__VA_OPT__ is a new feature of variadic macros in C++20. It lets you optionally insert tokens depending on if a variadic macro is invoked with additional arguments. An example usage is comma elision in a standardized manner.

What are the macros that are designed to support variable length arguments?

To support variable length arguments in macro, we must include ellipses (…) in macro definition. There is also “__VA_ARGS__” preprocessing identifier which takes care of variable length argument substitutions which are provided to macro.


2 Answers

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 + ...);
}
like image 57
NathanOliver Avatar answered Oct 06 '22 14:10

NathanOliver


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
like image 38
Richard Avatar answered Oct 06 '22 15:10

Richard