如何一起使用C ++和C可变参数?

时间:2013-05-15 11:27:03

标签: c++ templates c++11 variadic-templates variadic-functions

通常,在函数中使用C ++ 11可变参数模板功能要求基于可变参数的函数参数是函数参数列表中的最后一个。有一个例外;如果存在C级可变参数,它们是倒数第二个参数,它们必须是最后的。

template < typename ...Args >
int  super_printf( Something x, Args &&...a, ... );

我有时会随意思考C ++,我想知道如何实现这样的功能。我首先考虑通常从 a 递归剥离参数,然后我记得C级varargs没有级联。我必须马上把它们变成一个明确的va_list。

template < typename ...Args >
int  super_vaprintf( Something x, std::va_list &aa, Args &&...a );
// Note that "aa" is passed by reference.

template < typename ...Args >
int  super_printf( Something x, Args &&...a, ... )
{
    std::va_list  args2;
    int           result;

    va_start( args2, XXX );  // (A)
    try {
        result = super_vaprintf( x, args2, std::forward<Args>(a)... );
    } catch ( ... ) {
        va_end( args2 );  // (1)
        throw;
    }
    va_end( args2 );  // (2)
    return result;

    // Can (1) and (2) be compacted with RAII using a custom deleter lambda
    // in std::unique_ptr or something?  Remember that "va_end" is a macro!
}

通常的C ++可变参数递归剥离发生在super_vaprintf调用中。在(A)行,XXX,“a”或“a ......”的位置是什么?如果 a 为空,会发生什么? x 会转到那里吗?如果最后一个问题是真的,如果没有 x ,我们是否会被搞砸;除了可变参数之外没有其他参数? (如果确实如此,当 a 为空时我们如何使代码使用 x ,否则 a ?)

...

我在这里查看了我的C ++ 11标准副本以获得任何帮助。似乎没有。这会提示请求C ++委员会回来解决这个问题,但是我不确定在没有C ++ varargs占用所有内容的情况下可以调用这样的函数。我错了吗;可以调用函数来同时使用C ++和C varargs吗?或者就Stupid(模板)实例化技巧而言,混合仅对声明有用吗?

2 个答案:

答案 0 :(得分:4)

当您调用最后一个参数为包的函数时,所有参数都将成为该包的一部分。 va_args没有任何内容。您对显式模板参数的使用会产生误导,因为它们不是唯一的;它们只是隐含在隐含的论点之前。

要取消扣除,您需要参考:

(& super_printf<int, int>) ( 0L, 1, 2, 3, 4, 5 )

这是相当做作的,但现在你有问题没有传递给va_start

要为用户提供合理的界面,只需在两个列表之间添加参数即可。

struct va_separator {}; // Empty; ABI may elide allocation.

template < typename ...Args >
int  super_printf( Something x, Args &&...a, va_separator, ... );

这个super_printf将需要两个显式参数来定义包和一个显式的分隔符参数。但是你也可以提供一个公共函数,它通过pack接收它的所有参数,然后找到分隔符并使用包含分隔符之前的pack元素的显式参数列表转发到super_printf

答案 1 :(得分:0)

我已经在GCC 4.8的编译网站(Coliru)上尝试了这个代码,结果看起来很暗淡。我不知道它是否特别是GCC,或者是否所有其他编译器都做了类似的事情。那些使用其他编译器(Clang,Visual C ++,Intel等)的人可以试试这个吗?

#include <cstdarg>
#include <iostream>
#include <ostream>
#include <utility>

template < typename ...Args >
int  super_vaprintf( long, std::va_list &, Args &&... )
{
    return 17;
}

template < typename ...Args >
int  super_printf( long x, Args &&...a, ... )
{
    std::va_list  args2;
    int           result;

    va_start( args2, a );  // (A)
    try {
        result = super_vaprintf( x, args2, std::forward<Args>(a)... );
    } catch ( ... ) {
        va_end( args2 );
        throw;
    }
    va_end( args2 );
    return result;
}

int main() {
    std::cout << super_printf<int, int>( 0L, 1, 2, 3, 4, 5 ) << std::endl;  // (B)
    return 0;
}

对(B)行的super_printf的调用显式地将C ++变量设置为两个int条目。这将使函数使用参数12作为C ++ varargs,后三者作为C varargs。

在第(A)行,编译器坚持认为其中a的代码在某处有“...”。所以我将其改为:

va_start( args2, a... );  // (A)

我得到了关于错误数量的参数的另一个错误。这是有道理的,因为a扩展为两个参数。如果我将行(B)更改为一个C ++ vararg:

std::cout << super_printf<int>( 0L, 1, 2, 3, 4, 5 ) << std::endl;  // (B)

它运作得很好。如果我完全删除C ++ varargs:

std::cout << super_printf<>( 0L, 1, 2, 3, 4, 5 ) << std::endl;  // (B)

我们再次得到错误的数字或参数错误,因为a的长度为零)。如果我们在a为空时执行此操作:

va_start( args2, x /*a...*/ );  // (A)

代码再次起作用,尽管有一条警告x不是最后一个命名参数。

我们可以用另一种方式接近这个例子。让我们重置为:

va_start( args2, a... );  // (A)
//...
std::cout << super_printf( 0L, 1, 2, 3, 4, 5 ) << std::endl;  // (B)

其中第一个之后的所有参数都被分组为C ++ varargs。当然,我们在va_start中得到了相同的多参数错误。我逐渐评论出尾随的论点。当剩下两个参数时,它会起作用(这使得a只有一个参数)。

当只剩下一个参数时,也会出现错误,但错误消息会更改为明确说“太少”参数而不是“错误数量”。和以前一样,我在行(A)中为“a...”切换了“x”,代码被接受,但没有警告。因此,当我在行(B)中为<Whatever>明确包含“super_printf”时,我得到的解析器错误路径与我不包含它们时的路径不同,尽管两条路径都得出相同的结论

是时候告诉委员会他们忽略了什么......