简体   繁体   中英

How can C++ and C variadic arguments be used together?

Generally, using the C++11 variadic template feature with functions requires the variadic-based function arguments to be the last in the function argument list. There is one exception; they are the next-to-last arguments if there are C-level variadic arguments, which must be dead last.

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

I sometimes randomly think about C++, and I wondered how such a function can be implemented. I first thought about the usual recursive peeling of arguments from a , then I remembered that the C-level varargs don't cascade. I have to turn them to a definitive va_list right away.

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!
}

The usual C++ variadic recursive peeling happens in the super_vaprintf call. At line (A), what goes in the place of XXX , "a" or "a..."? What happens if a is empty, does x go there instead? If that last question's true, are we screwed if there's no x ; that there's no arguments besides the variadic ones? (And if it's true, how do we conditionalize the code to use x when a is empty, and a otherwise?)

...

I just looked at my copy of the C++11 standard for any assistance here. There doesn't seem to be any. This would prompt a request for the C++ committee to come back to fix this, but I'm not sure that there's any way such a function could be called without the C++ varargs taking everything. Am I wrong; can a function call be made to use both C++ and C varargs? Or is mixing only useful for declarations, in terms of Stupid (Template) Instantiation Tricks?

When you call a function whose last parameter is a pack, all the arguments become part of that pack. There is nothing left for va_args . Your usage of explicit template arguments is misleading because they are not exclusive; they simply precede implicit arguments.

To defeat deduction, you need a reference:

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

This is fairly contrived, but now you have the problem of nothing to pass to va_start .

To provide a reasonable interface to users, just add a parameter between the two lists.

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

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

This super_printf will need both explicit arguments to define the pack and an explicit separator argument. But you can alternatively provide a public function which receives all its arguments by pack, then finds the separator and forwards to super_printf using an explicit argument list comprising the pack elements before the separator.

I've tried this code out on a compiling web site (Coliru), with GCC 4.8, and the results look bleak. I don't know if it's GCC in particular, or if all the other compilers out there do something similar. So can people with other compilers (Clang, Visual C++, Intel, etc.) try this out?

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

The call to super_printf on line (B) explicitly sets the C++ varargs to two int entries. This will make the function use arguments 1 and 2 as C++ varargs and the latter three as C varargs.

On line (A), the compiler insists that the code with a in it have a " ... " somewhere. So I change it to:

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

I get another error about having the wrong number of arguments. It makes sense since a expands to two arguments. If I change line (B) to one C++ vararg:

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

it works just fine. If I remove the C++ varargs entirely:

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

we get the wrong-number-or-arguments error again, because a has length of zero). If we do this when a is empty:

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

the code works again, although there is a warning about x not being the last named parameter.

We can approach the example in another way. Let's reset to:

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

where all the arguments after the first are grouped as C++ varargs. We get the same too-many-arguments error in va_start , of course. I progressively comment out trailing arguments. It works when there are exactly two arguments left (which makes a have exactly one argument).

There is also an error when there's only one argument left, but the error message changes to explicitly say "too few" arguments instead of "wrong amount." Like before, I switched out " a... " for " x " in line (A), and the code was accepted, but there was no warning. So it seems that when I explicitly include " <Whatever> " for super_printf in line (B) I get a different parser error path than when I don't include them, although both paths go to the same conclusion.

Time to tell the committee that they overlooked something....

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM