13

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

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

我有时会随机想到 C++,我想知道如何实现这样的功能。我首先想到了通常从a中递归剥离参数,然后我记得 C 级可变参数不会级联。我必须立即将它们转换为明确的 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++ 可变参数的情况下调用这样的函数。我错了吗; 可以进行函数调用以同时使用 C++ 和 C 可变参数吗?或者就愚蠢(模板)实例化技巧而言,混合仅对声明有用?

4

2 回答 2

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将需要显式参数来定义包和显式分隔符参数。但是您也可以提供一个公共函数,该函数通过包接收其所有参数,然后找到分隔符并转发到super_printf使用包含分隔符之前的包元素的显式参数列表。

于 2013-10-29T04:47:06.637 回答
0

我已经在编译网站 (Coliru) 上使用 GCC 4.8 尝试过这段代码,结果看起来很惨淡。我不知道它是否特别是 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;
}

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

在 (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++ 可变参数:

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++ 可变参数。当然,我们在 中得到相同的过多参数错误va_start。我逐渐注释掉尾随的论点。它在只剩下两个参数时起作用(这使得a只有一个参数)。

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

是时候告诉委员会他们忽略了一些事情......

于 2013-05-16T09:17:09.997 回答