Passing std::vector items to variadic function

前端 未结 5 1705
天涯浪人
天涯浪人 2021-01-17 17:26

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:

相关标签:
5条回答
  • 2021-01-17 17:38

    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.

    0 讨论(0)
  • 2021-01-17 17:56

    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

    0 讨论(0)
  • 2021-01-17 17:59

    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. :)

    0 讨论(0)
  • 2021-01-17 17:59

    Judging by your own answer given, it sounds like you could make use of boost format.

    Examples:

    #include <iostream>
    #include <string>
    #include <sstream>
    #include <boost/format.hpp>
    using namespace std;
    using namespace boost;
    
    template <typename T>
    string formatted_str_from_vec(const T& v) {
        ostringstream fs;
        size_t count = 1;
        for (const auto& i : v) {
            if (&i != &v[0]) {
                fs << " ";
            }
            fs << '%' << count << '%';
            ++count;
        }
        format fmtr(fs.str());
        for (const auto& i : v) {
            fmtr % i;
        }
        // looks like fmtr("%1% %2% %3% %4%") % v[0] % v[1] etc.
        return fmtr.str();
    }
    
    int main() {
        cout << formatted_str_from_vec(vector<int>({1, 2, 3, 4, 5, 6, 7, 8, 8, 10, 11, 12})) << endl;
        cout << formatted_str_from_vec(vector<string>({"a", "b", "c"})) << endl;
        format test1("%1% %2% %3%");
        test1 % 1 % "2" % '3';
        cout << test1.str() << endl;
        format test2("%i %s %c");
        test2 % 1 % "2" % '3';
        cout << test2.str() << endl;
        format test3("%1% %2%");
        test3.exceptions(io::no_error_bits);
        test3 % 'g';
        cout << test3.str() << endl;
        format test4("%%1%% = %1%");
        test4 % "zipzambam";
        cout << test4.str() << endl;
    }
    
    // g++ -Wall -Wextra printvector.cc -o printvector -O3 -s -std=c++0x
    

    Of course, none of that's necessary to just print out a vector.

    0 讨论(0)
  • 2021-01-17 18:03

    You may use STL algorithm for_each to print every item of vector.

    0 讨论(0)
提交回复
热议问题