是否可以将va_list传递给variadic模板

Is it possible to pass va_list to variadic template?

本文关键字:variadic 模板 list va 是否      更新时间:2023-10-16

我知道va_list通常是您应该避免的,因为它不是很安全,但是可以通过以下功能传递参数:

void foo(...);

之类的函数
template<typename... Args>
void bar(Args... arguments);

编辑:最初,我想尝试使用它来调用具有可变数量参数/类型的虚拟函数,但这不是使这个问题无关紧要的方法。最终我最终做了这样的事情:

struct ArgsPackBase
{
    virtual ~ArgsPackBase() {}
};
template<typename... Args>
struct ArgsPack : public ArgsPackBase
{
public:
    ArgsPack(Args... args_)
        : argsTuple(args_...)
    {}
    void call(std::function<void(Args...)> function)
    {
        callExpansion(function, std::index_sequence_for<Args...>{});
    }
private:
    template<std::size_t... I>
    void callExpansion(std::function<void(Args...)> function, std::index_sequence<I...>)
    {
        function(std::get<I>(argsTuple)...);
    }
    std::tuple<Args...> argsTuple;
};

no,variadic函数参数是一个运行时功能,并且您将您传递给variadic模板的参数数量(尽管可变)必须在编译时知道。

如RFC1925所观察到的,"有足够的推力,猪会飞得很好。但是,这不一定是一个好主意。"

正如Piotr olszewski指向的那样,旧的C风格variadic函数参数是一个旨在在运行时工作的功能。新的variadic模板C - 汇编时间的样式工作。

所以...只是为了好玩...我想如果您知道,可以编译时间,foo()的参数类型。

示例,如果 foo()是一个variadic模板函数,则在以下示例中像 foo()一样……该编译并与clang 一起使用,但使用g 给出汇编错误...而我不知道谁是对的(当我时有时间,我会为此打开一个问题)...

#include <cstdarg>
#include <iostream>
#include <stdexcept>
template <typename ... Args>
void bar (Args const & ... args)
 { 
   using unused = int[];
   (void)unused { (std::cout << args << ", ", 0)... };
   std::cout << std::endl;
 }
template <typename ... Ts>
void foo (int num, ...)
 {
   if ( num != sizeof...(Ts) )
      throw std::runtime_error("!");
   va_list args;                     
   va_start(args, num);           
   bar( va_arg(args, Ts)... );
   va_end(args);
 }
int main ()
 {
   foo<int, long, long long>(3, 1, 2L, 3LL); // print 1, 2, 3, 
 }

观察到您需要在foo()中传递一个补给信息:variadic参数的数量:va_start语法要求您传递具有sizeof...(Ts)值相同值的变量(num)。

但是,我重复一遍,只是为了好玩。

为什么出于善意,当我们可以直接编写bar()之类的函数时,我们应该写下像foo()这样的函数?

对于C 模板,编译器必须在编译时产生每个实例。因此,对于每个参数组合(int,double,float),相应的实例应显示在对象文件中。

您的foo不可能知道每个参数组合,因为有无限的金额 - 因此,除非您以某种方式限制参数空间,否则您的问题的答案是"否"。

但是,使用某些模板魔术是可能的,但实际上并不有用。我以一个特定的示例作为概念证明,但是请不要在真实代码。

中使用它。

void foo(const char* s, ...);

期望像"ffis"这样的格式字符串,其中每个字符都指定参数类型(在这种情况下为double,double,double,整数,字符串)。我们还具有一个variadic模板bar函数,该函数打印其参数:

template <typename Arg, typename... Args>
void doPrint(std::ostream& out, Arg&& arg, Args&&... args)
{
    out << std::forward<Arg>(arg);
    using expander = int[];
    (void)expander {
        0, (void(out << ", " << std::forward<Args>(args)), 0)...
    };
    out << 'n';
}
void bar() {
    std::cout << "no argumentsn";
}
template<typename... Args>
void bar(Args... arguments) {
    doPrint(std::cout, arguments...);
}

对于foo工作,我们将在编译时间生产每个可能的参数组合,直至长度N(SO,3^n实例):

//struct required to specialize on N=0 case
template<int N>
struct CallFoo {
    template<typename... Args>
    static void foo1(const char* fmt, va_list args, Args... arguments) {
        if (*fmt) {
            using CallFooNext = CallFoo<N - 1>;
            switch (*fmt) {
            case 'f':
            {
                double t = va_arg(args, double);
                CallFooNext::foo1(fmt + 1, args, arguments..., t);
            }break;
            case 'i':
            {
                int t = va_arg(args, int);
                CallFooNext::foo1(fmt + 1, args, arguments..., t);
            }break;
            case 's':
            {
                const char* t = va_arg(args, const char*);
                CallFooNext::foo1(fmt + 1, args, arguments..., t);
            }break;
            }
        } else {
            bar(arguments...);
        }
    }
};
template<>
struct CallFoo<0> {
    template<typename... Args>
    static void foo1(const char* fmt, va_list args, Args... arguments) {
        bar(arguments...);
    }
};

void foo(const char* fmt, ...) {
    va_list args;
    va_start(args, fmt);
    //Here we set N = 6
    CallFoo<6>::foo1<>(fmt, args);
    va_end(args);
}

主要功能,用于完整性:

int main() {
  foo("ffis", 2.3, 3.4, 1, "hello!");
}

由此产生的代码在我的计算机上使用gcc编译约10秒,但生成正确的字符串2.3, 3.4, 1, hello!