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

CTM*_*ser 11 c++ templates variadic-functions variadic-templates c++11

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

template < typename ...Args >
int  super_printf( Something x, Args &&...a, ... );
Run Code Online (Sandbox Code Playgroud)

我有时会随机思考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!
}
Run Code Online (Sandbox Code Playgroud)

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

...

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

Pot*_*ter 5

当您调用最后一个参数是包的函数时,所有参数都将成为该包的一部分。已经没有什么可以留下的了va_args。您对显式模板参数的使用具有误导性,因为它们不是排他性的;它们只是位于隐式参数之前。

为了击败演绎,你需要一个参考:

(& super_printf<int, int>) ( 0L, 1, 2, 3, 4, 5 )
Run Code Online (Sandbox Code Playgroud)

这是相当人为的,但现在你遇到了没有什么可以传递给 的问题va_start

为了给用户提供一个合理的接口,只需在两个列表之间添加一个参数即可。

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

template < typename ...Args >
int  super_printf( Something x, Args &&...a, va_separator, ... );
Run Code Online (Sandbox Code Playgroud)

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


CTM*_*ser 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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

va_start( args2, a... );  // (A)
Run Code Online (Sandbox Code Playgroud)

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

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

它工作得很好。如果我完全删除 C++ 可变参数:

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

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

va_start( args2, x /*a...*/ );  // (A)
Run Code Online (Sandbox Code Playgroud)

代码再次运行,尽管有一条关于x不是最后一个命名参数的警告。

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

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

其中第一个参数之后的所有参数都分组为 C++ 可变参数。va_start当然,我们在 中也遇到了同样的太多参数错误。我逐渐注释掉后面的参数。当只剩下两个参数时(这使得a只有一个参数)它起作用。

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

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