将std::vector<int>项目传递给可变参数函数

10
我使用gcc 4.6。 假设有一个向可变参数函数f(const char* format, ...)传递参数的向量v。
其中一种方法是:
        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);
        }

问题显然是它不支持向量v中任意数量的项。 然而,我相信我已经理解了va_lists的工作原理, 即通过从堆栈中读取参数,从“...”之前的最后一个命名参数的地址开始, 现在我认为应该可以将向量项目值复制到内存块(例如myMemBlock)中 并将其地址作为'format'后面的第二个参数传递。 显然,这需要使myMemBlock像堆栈一样被f()正确地结构化。

  1. 这样的事情可行吗?
  2. 或者,是否可能通过一些内联汇编器魔法将向量项目值推到实际堆栈上, 调用函数f()并在清理完堆栈后?

最后,我不关心的事情:

  1. 代码可能不可移植。好吧,我只对gcc感兴趣。
  2. 可能会涉及利用预处理器进行hackery的其他方法。
  3. 不鼓励使用类似于printf()的变参函数来进行格式化的用法。
  4. 不鼓励使用变参模板函数。

1
据我所知,无法“伪造”自己的va_arg列表。你可以做的是通过重载你的函数f()直接传递向量。 - Gui13
我不想重新实现va_arg,我只想模仿一个堆栈供va_arg操作。 - user1142580
5个回答

4
http://cocoawithlove.com/2009/05/variable-argument-lists-in-cocoa.html 中有一个“Creating a fake va_list”部分,虽然是为Cocoa而写的,但您可以在网络上找到一些关于GCC的内容。
然后,我猜测您可能会这样做:
#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);
}

但是,我完全没有头绪。 :)

看起来非常有趣!谢谢!我会报告结果。 - user1142580

3

好的,这里是部分解决方案! 部分原因是它不适用于真正的可变参数函数,而是适用于接受va_list作为参数的函数。 但我认为完整的解决方案并不遥远。

它基于我在这里找到的示例:

  1. 动态创建va_list https://bbs.archlinux.org/viewtopic.php?pid=238721

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

该代码已经在Linux上使用gcc和VC++2008成功测试过了,其他平台也可能得到支持,但这取决于您。

对我来说重要的见解是va_list基本上只是一个紧凑的数组,可以动态填充数据,并可以将其作为参数传递给像vprintf、vfprintf、vsprintf这样的函数。

因此,将向这些函数之一传递向量项可以通过在调用之前为向量项分配足够的内存并将其复制过去来实现。

话虽如此,这里是动态分配堆栈方法

#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;
}

猜猜它是做什么的? 它允许使用 vector 收集参数进行 printf(..) 样式的格式化。 是的,它并不完美,但它做到了我想要的。 此外,它覆盖了两个主要平台:D
另外,看一下这篇文章: va_pass http://www.codeproject.com/Articles/9968/va_list-va_start-va_pass-or-how-to-pass-variable-a

好的,可以通过使用此方法来实现完整的答案:http://www.codeproject.com/Articles/9968/va_list-va_start-va_pass-or-how-to-pass-variable-a 但是,由于使用了不同的许可证,我不能简单地在这里复制代码。 - user1142580

2

您的思考方式没有达到正确的抽象层次。

当您说要将向量转换为可变参数列表时,是因为接受可变参数列表的函数对您有所帮助。

因此,真正的问题是,我该如何从vector开始做与f相同的事情?

将调用转发给f可能会成为解决方案,但这并不明显。

如果只是打印:

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

或者,如果您可以访问外部库:

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

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

在这一点上,f的兴趣不再明显。


假设这只是关于打印的问题。为了简单起见,我将v定义为vector<int>。然而,这只是我自己的一个起点。如果v被更改以包含不同的POD类型,我想利用vprintf()格式化。 - user1142580

1
根据您所给出的答案,似乎您可以使用boost format
示例:
#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

当然,要只打印出一个向量,这些都是不必要的。

我提到这是一个部分解决方案。完整的解决方案,可以通过使用我在自己的答案下评论的va_pass来实现。你的建议在许多情况下可能有帮助(即在boost可用或受欢迎的情况下),因此"投票支持"。 - user1142580

0
你可以使用STL算法for_each来打印vector的每个元素。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接