Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing std::vector<int> items to variadic function

I'm using gcc 4.6. Assume that there is a vector v of parameters I have to pass to a variadic function f(const char* format, ...).

One approach of doing this is:

        void VectorToVarArgs(vector<int> &v)
        {
            switch(v.size())
            {
                case 1: f("%i",             v[0]);
                case 2: f("%i %i",          v[0], v[1]);
                case 3: f("%i %i %i",       v[0], v[1], v[2]);
                case 4: f("%i %i %i %i",    v[0], v[1], v[2], v[3]);

                // etc...
                default:
                    break;
            }
        }

        // where function f is
        void f(const char* format, ...)
        {
            va_list args;
            va_start (args, format);
            vprintf (format, args);
            va_end (args);
        }

The problem is of course that it does not support an arbitrary number of items in the vector v. However, I believe to have understood how va_lists works in principle, i.e. by reading the arguments from the stack, starting at the address of the last named argument prior to "...", Now I thought it should be possible to copy the vector item values to a memory block (e.g. myMemBlock) and pass it's address as the second argument after 'format'. Obviously that would require that myMemBlock to be structured as expected by f(), i.e. like a stack.

  1. Is such a thing doable?
  2. Alternatively, is it possible to push the vector item values on the real stack with some inline assembler magic, call function f() and clean up the stack afterwards?

Finally, things I don't care about:

  1. The code might be not portable. OK I'm just interested in gcc.
  2. There might be other approaches involving preprocessor hackery.
  3. Usage of variadic function for formatting like printf() does, is discouraged for C++.
  4. Usage of variadic template functions.
like image 762
user1142580 Avatar asked Feb 14 '12 12:02

user1142580


3 Answers

There's a "Creating a fake va_list" section at http://cocoawithlove.com/2009/05/variable-argument-lists-in-cocoa.html. It's for Cocoa, but you might be able to find something on the net for GCC.

Then, I'm guessing you'd do something like this:

#include <string>
#include <cstdio>
#include <vector>
#include <cstdarg>
using namespace std;

struct my_list {
   unsigned int gp_offset;
   unsigned int fp_offset;
   void *overflow_arg_area;
   void *reg_save_area;
};

void f(const char* format, ...) {
    va_list args;
    va_start (args, format);
    vprintf (format, args);
    va_end (args);
}

void test(const vector<int>& v) {
    string fs;
    for (auto i = v.cbegin(); i !=v.cend(); ++i) {
        if (i != v.cbegin()) {
            fs += ' ';
        }
        fs += "%i";
    }
    my_list x[1];
    // initialize the element in the list in the proper way
    // (however you do that for GCC)
    // where you add the contents of each element in the vector 
    // to the list's memory
    f(fs.c_str(), x);
    // Clean up my_list
}

int main() {
    const vector<int> x({1, 2, 3, 4, 5});
    test(x);
}

But, I have absolutely no clue. :)

like image 113
Shadow2531 Avatar answered Oct 31 '22 15:10

Shadow2531


Your reflexion is not at the right level of abstraction.

When you say you want to convert the vector to a variable arguments lists, it is because the function that takes the variable argument list does something of interest to you.

The real question is therefore, how could I do the same thing than f, but starting from a vector ?

It is possible than forwarding the call to f might end up begin the solution, but it is not obvious.

If it is just about printing:

void f(std::vector<int> const& vi) {
   bool first = true;
   for (int i: vi) {
     if (first) { first = false; } else { std::cout << ' '; }
     std::cout << i;
   }
}

Or, if you have access to outside libraries:

#include <boost/algorithm/string/join.hpp>

void f(std::vector<int> const& vi) {
  std::cout << boost::join(vi, " ");
}

At which point the interest of f is not really evident any longer.

like image 2
Matthieu M. Avatar answered Oct 31 '22 17:10

Matthieu M.


Okay, here is a partial solution! Partial, because it does not apply to really variadic functions, but to those which accept a va_list as argument. But I think the full solution isn't far away.

It is based on the examples I found here:

  1. Dynamically create a va_list https://bbs.archlinux.org/viewtopic.php?pid=238721

  2. forge a va_list http://confuseddevelopment.blogspot.com/2006/04/dynamically-creating-valist-in-c.html

This code is tested with gcc on linux and VC++2008 successfully, other platforms might be supported too, but that's up to you.

The important insight for me was that a va_list is basically nothing more than a packed array, which can be filled with data dynamically and can be passed to functions like vprintf, vfprintf, vsprintf which accept it as argument.

So passing vector items to one of those functions can work by allocating enough memory for the vector items and copy them over prior to the call.

Having said that, here is the dynamically allocating stack approach:

#include <iostream>
#include <stdio.h>
#include <stdarg.h>
#include <string>
#include <vector>
#include <alloca.h>

using namespace std;


class Format
{
    typedef vector<unsigned long> ULVector;
    ULVector _args;
    string _format;

    public:
        Format(const char* format) : _format(format)
        {}

        Format &operator<<(int arg) {
            _args.push_back((unsigned long)arg);
            return *this;
        }

        Format &operator<<(const char* arg) {
            _args.push_back((unsigned long)arg);
            return *this;
        }

        string format() {
            union {
                va_list varargs;
                unsigned long* packedArray;
            } fake_va_list;

            // malloc would do it as well!
            // but alloca frees the mem after leaving this method
            unsigned long *p = (unsigned long*)alloca(_args.size() * sizeof(unsigned long));
            fake_va_list.packedArray = p;

            ULVector::iterator i = _args.begin();
            for (int n=0; i != _args.end(); i++, n++) {
                p[n] = *i;
            }

            char buffer[512];
            const char* fmt = _format.c_str();
            vsprintf(buffer, fmt, fake_va_list.varargs);

            // place a free(p) here if you used malloc
            return string(buffer);
        }
};


ostream& operator <<=(ostream &os, Format &obj) {
      os << obj.format();
      return os;
}


int main()
{
    // we use '<<=' operator here which has lower precedence than '<<'
    // otherwise we have to write
    // cout << ( Format("\n%x %s %x %c\n") <<  etc. );
    cout <<= Format("\n%x %s %x %c\n") << 0x11223344 << "VectorToVarArg" << 0xAABBCCDD << '!';
    return 0;
}

Guess what it does? It allows printf(..) style formatting with the parameters collected in a vector. Yes, it's not perfect but it does what I wanted. Furthermore, it covers two major platforms :D

Additionally, have look at this article: va_pass http://www.codeproject.com/Articles/9968/va_list-va_start-va_pass-or-how-to-pass-variable-a

like image 2
user1142580 Avatar answered Oct 31 '22 15:10

user1142580