问题
I know that va_list
is usually something you should avoid since its not very safe, but is it possible to pass the arguments from a function like:
void foo(...);
to a function like
template<typename... Args>
void bar(Args... arguments);
?
edit: Originally I wanted to try to use this to call a virtual function with a variable amount of arguments / types, but this was not the way to go making this question kind of irrelevant. Eventually I ended up doing something like this:
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;
};
回答1:
No, variadic function arguments are a runtime feature, and the number of arguments you pass to a variadic template, although variable, must be known at the compile time.
回答2:
As observed in RFC1925, "With sufficient thrust, pigs fly just fine. However, this is not necessarily a good idea."
As pointed by Piotr Olszewski, the old C-style variadic function arguments is a feature intended to work at run-time; the new variadic template C++-style work at compile time.
So... just for fun... I suppose it can be possible if you know, compile time, the types of the argument for foo()
.
By example, if foo()
is a variadic template function like the foo()
in the following example... that compile and work with clang++ but give a compilation error with g++... and I don't know who's right (when I have time, I'll open a question about this)...
#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,
}
Observe that you need to pass a reduntant information in foo()
: the number of the variadic arguments: the va_start
syntax require that you pass a variable (num
) with the same value of sizeof...(Ts)
.
But, I repeat, just for fun.
Why, for goodness sake, we should write a function like foo()
when we can directly write a function like bar()
?
回答3:
For C++ template, compiler must produce every instance at compile time. So, for every parameter combination (int,double,float)
, corresponding instance should appear in object file.
It is not possible for your foo
to know every parameter combination, as there are infinite amount - so unless you restrict parameter space somehow, the answer to your question is "no".
However, with some template magic it is possible, but not practically useful. I show one specific example as a proof of concept, but please, do not use this in real code.
Lets say
void foo(const char* s, ...);
expects format string like "ffis"
, where every character specifies a parameter type (double, double, integer, string in this case). We also have a variadic template bar
function which prints its arguments:
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 arguments\n";
}
template<typename... Args>
void bar(Args... arguments) {
doPrint(std::cout, arguments...);
}
For foo
to work, we will produce at compile time every possible parameter combination up to length N
(so, 3^N instances):
//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);
}
Main function, for completeness:
int main() {
foo("ffis", 2.3, 3.4, 1, "hello!");
}
Resulting code compiles about 10 seconds with gcc
on my machine, but produces the correct string 2.3, 3.4, 1, hello!
来源:https://stackoverflow.com/questions/42317895/is-it-possible-to-pass-va-list-to-variadic-template