jef
jef

Reputation: 4083

How do I pass a variable length array in a function which receives a variable length array in C++?

I found a way to pass a variable length array in C++. But it fails 'wrap' function in below code. Actually I want to wrap format function in my project. What am i doing wrong in my code?

test code

#include <iostream>
#include <stdarg.h>
#include <string>

void log(const char* format, ...)
{
    va_list argptr;
    va_start(argptr, format);

    int length = _vscprintf(format, argptr);
    char* buf_ = new char [length + 1];
    int ret = vsnprintf(buf_, 1000, format, argptr);

    if (ret >= 0) {
        std::cout << buf_ << std::endl;
    }

    delete[] buf_;
    va_end(argptr);
}

void wrap(const char *format, ...)
{
    va_list ap;
    va_start(ap, format);
    log(format, ap);
    va_end(ap);
}


int main()
{
    log( "direct = %d", 1);
    wrap("wrap   = %d", 1);

    return 0;
}

the result is here.

direct = 1
wrap   = 15137088 // what's happen?

Upvotes: 0

Views: 123

Answers (3)

Useless
Useless

Reputation: 67752

I found a way to pass a variable length array in C++

That isn't a variable-length array, and it isn't really idiomatic C++. The ... is a variable-length argument list, and is available in C.

The simplest reasonable way to wrap your log function is the variadic template one, which can simply be written as:

template <typename... Args>
void wrap(const char *format, Args&&... args) {
    log(format, std::forward<Args>(args)...);
}

In the log function itself, vsnprintf returns the number of bytes that would have been written, in the event it fills the buffer. So, you can always just call it once with an optimistic buffer size, and grow the buffer if necessary: you don't need the non-standard _vscprintf. That would look something like:

void log(const char* format, ...)
{
    va_list argptr;
    va_start(argptr, format);

    static const size_t DefaultSize = 200;
    // pick some value that makes sense ^^ here
    char buf[DefaultSize];
    int rv = vsnprintf(buf, DefaultSize, format, argptr);
    if (rv < 0) {
        // we can't return errors with this prototype:
        // should it throw?
        return;
    }
    if (rv >= DefaultSize) {
        vector<char> dynbuf(rv+1);
        rv = vsnprintf(&dynbuf[0], dynbuf.size(), format, argptr);
        std::cout << &dynbuf[0] << std::endl;
    } else {
        std::cout << buf << std::endl;
    }

    va_end(argptr);
}

Note also that wrap knows the types of all its arguments, but that information is discarded when you call the C-style variadic function log. You might consider Boost.Format as a type-safe alternative - as a bonus, it will manage the buffer for you.

Upvotes: 1

hlscalon
hlscalon

Reputation: 7552

In c++11, you can use variadic templates

#include <iostream>

void tprintf(const char* format) // base function
{
    std::cout << format;
}

template<typename T, typename... Targs>
void tprintf(const char* format, T value, Targs... Fargs) // recursive variadic function
{
    for ( ; *format != '\0'; format++ ) {
        if ( *format == '%' ) {
           std::cout << value;
           tprintf(format+1, Fargs...); // recursive call
           return;
        }
        std::cout << *format;
    }
}

int main()
{
    tprintf("direct = %\n", 1);
    tprintf("wrap   = %\n", 1);
    return 0;
}

Upvotes: 0

Scott Langham
Scott Langham

Reputation: 60371

Passing a va_list where a variable number of argumets (x, y, z) is expected isn't designed to work.

To achieve what you want, you need to do something like this:

void log_args(const char* format, va_list& argptr)
{
    // I'm unsure on this... you may possibly need to make a separate
    // copy of the va_list to pass in to each of _vscprintf and vsnprintf.  
    va_list second;
    va_copy(second, argptr);

    int length = _vscprintf(format, argptr);
    char* buf_ = new char [length + 1];

    int ret = vsnprintf(buf_, 1000, format, second);

    if (ret >= 0) {
        std::cout << buf_ << std::endl;
    }

    delete[] buf_;
}

void log(const char* format, ...)
{
    va_list argptr;
    va_start(argptr, format);    
    log_args(argptr);
    va_end(argptr);
}

void wrap(const char *format, ...)
{
    va_list ap;
    va_start(ap, format);
    log_args(format, ap);
    va_end(ap);
}

In this example 'wrap' and 'log' appear the same... but I presume you want to do something additional in your real wrap function otherwise why would you be asking this question.

Upvotes: 0

Related Questions