7

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;
};
Andreas Loanjoe
  • 2,205
  • 10
  • 26
  • 5
    No, that's not possible. – Kerrek SB Feb 18 '17 at 16:55
  • How would that make it any saver? Once the variables are in a `...`, every type info is lost, which is exactly what makes it unsave. This loss is not recoverable. Note that of course you can explicitly cast the contents of the `va_list` to some types that will then be picked up by variadic templates, but nothing will recover the lost type info for you automatically. – Ulrich Eckhardt Feb 18 '17 at 17:56

3 Answers3

7

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.

4

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()?

max66
  • 65,235
  • 10
  • 71
  • 111
  • I write foo because foo can be virtual, but this isnt going to work I'm looking at storing parameter packs now, getting closer thanks for the help though – Andreas Loanjoe Feb 18 '17 at 20:00
  • @AndreasLoanjoe - not sure to understand; I suppose you should edit your question to show an example of what do you want; anyway: this solution is really silly; it's just for fun. – max66 Feb 18 '17 at 20:05
  • Well what I wanted to do was pass a variadic parameter pack to a derived class but I figured out a way to do it now without the ... va_list so the question is kind of irrelevant now, since I think va_list is pretty useless from what I read now. – Andreas Loanjoe Feb 19 '17 at 10:37
  • This can be done by downcasting this fella struct ArgsPackBase { virtual ~ArgsPackBase() {} }; template struct ArgsPack : public ArgsPackBase { public: ArgsPack(Args... args_) : argsTuple(args_...) {} void call(std::function function) { callExpansion(function, std::index_sequence_for{}); } private: template void callExpansion(std::function function, std::index_sequence) { function(std::get(argsTuple)...); } std::tuple argsTuple; }; – Andreas Loanjoe Feb 19 '17 at 10:38
  • Hum honestly this explains how std::format can template a compact print. Still that foo call without types is scary. Especially since your calling another function pilling from va_list. – Paul Bruner Apr 28 '23 at 21:43
1

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!