How Would One Call Std::Forward on All Arguments in a Variadic Function

How would one call std::forward on all arguments in a variadic function?

You would do:

template <typename ...Params>
void f(Params&&... params)
{
y(std::forward<Params>(params)...);
}

The ... pretty much says "take what's on the left, and for each template parameter, unpack it accordingly."

Forward variadic function arguments to another variadic function without cost

If we have c++11, why mess around with variadic argument lists?

#include <utility>

extern enum {LOG_MODE_LOCAL, LOG_MODE_REMOTE} m_logMode;

extern void LogDebugEx(const char*, ...);

template<class...Args>
void LogDebug(const char* zFormat, Args&&...args)
{

if (m_logMode == LOG_MODE_LOCAL) // Does not need to optimize this mode.
{
char zDesc[5000];
snprintf(zDesc, 5000, zFormat, args...);
// do what you have to do here
}
else // m_logMode == LOG_MODE_REMOTE, critical path
{
LogDebugEx(zFormat, std::forward<Args>(args)...); // Forwarded to new variadic function
}
}

Forward a specific range of arguments in a variadic function

Use an additional template parameter:

template <typename T, typename... Ts>
void test_variadic(T&& arg, Ts&&... args) {
test_simple(std::forward<Ts>(args)...);
}

That way, the first parameter is not part of the variadic.

how to forward variable number of arguments to another function?

The typical formulation of this in C is to have two functions, one that accepts ... and one that accepts a va_list (e.g., printf versus vprintf). In C++ it’s convenient to do this with overloads:

// public
void Logger::Error(const std::string& format, ...) {
va_list args;
va_start(args, format);
Error(format, args);
va_end(args);
}

// private
void Logger::Error(const std::string& format, va_list args) {
if (this != &errorsLogger)
errorsLogger.Error(format, args);

vfprintf(logFile, format.c_str(), args);
fprintf(logFile, "\n");
fflush(logFile);
}

Using C++11, it is possible to do directly with a variadic template. You can also forward arguments to C-style variadic functions.

template<class... Args>
void Logger::Error(const std::string& format, Args&&... args) {
if (this != &errorsLogger)
errorsLogger.Error(format, std::forward<Args>(args)...);

fprintf(logFile, format.c_str(), std::forward<Args>(args)...);
fprintf(logFile, "\n");
fflush(logFile);
}

Forwarding variadic list of arguments

They are not the same. They are the same in the case that the arity of args is 1 or 0. otherwise it will fail to compile, consider..

#include <iostream>
using namespace std;
template<typename T, typename ...U>
auto time_function_1(T&& func, U&& ...args)
{

std::cout<<"timing"<<std::endl;
auto val = std::forward<T>(func)(std::forward<U...>(args...));
std::cout<<"timing over"<<std::endl;
return val;
}

template<typename T, typename ...U>
auto time_function_2(T&& func, U&& ...args)
{

std::cout<<"timing"<<std::endl;
auto val = std::forward<T>(func)(std::forward<U>(args)...);
std::cout<<"timing over"<<std::endl;
return val;
}

int f (int){return 0;}

int y (int,int){return 0;}

int main() {
time_function_1(f,1);
time_function_2(f,1);

time_function_1(y,1,2); // fail
time_function_2(y,1,2);
return 0;
}

Demo

for the failing case std::forward<U...>(args...) expands to forward<int, int>(int&, int&) and will fail to compile.

std::forward<U>(args)... expands to std::forward<int>(int&),std::forward<int>(int&)

How to pass variadic function parameters to another function in C++ (specifically std::format)?

You don't want the C-style ... parameter. You want a variadic template:

template <typename T, typename ...P>
void log(T &&format, P &&... params)
{
std::string msg = fmt::format(std::forward<T>(format), std::forward<P>(params)...);
std::cout << msg << '\n';
}

Notice forwarding references for the parameter, pack instead of const references. Const references had caused problems for me before (spdlog used to use const references, and fmt::join didn't like them).

Notice the first parameter being templated. At least with libfmt, this is necessary to be able to receive FMT_STRING(...) parameters. With std::format, an std::string_view would probably suffice. const std::string & is uncool because it forces a heap allocation.

Also I'm not sure how I feel about having a separate parameter-less overload. It means that you have to escape { and } only when there's at least one parameter, which is not nice.

Also consider using spdlog, which is very similar. It wraps libfmt, and can direct output to various/arbirary targets.

Forward variadic arguments based on conditions

Your question muddies the waters a bit, because you're mixing runtime and compile-time logic, because in your case the number and types of function arguments depends on the runtime value of something.

You're now stuck with two different options: it is possible to make this work (even in just C++14), but any call site that doesn't pass the correct arguments will not be detected until runtime. The way to do so is with the help of std::enable_if_t, decltype() and std::declval. I've done this for your example, and the following compiles for me in C++14 mode:

#include <iostream>
#include <type_traits>
#include <stdexcept>

enum class Fruit {
Apple,
Orange,
Pear,
};

void processApple(int a, int b);
void processOrange(int c);
void processPear(double d);
void postProcessing();

struct Processor
{
template<typename... Ts, typename = decltype(processApple(std::forward<Ts>(std::declval<Ts>())...))>
static void process(std::integral_constant<Fruit, Fruit::Apple>, Ts&&... ts)
{
processApple(std::forward<Ts>(ts)...);
}

template<typename... Ts, typename = decltype(processOrange(std::forward<Ts>(std::declval<Ts>())...))>
static void process(std::integral_constant<Fruit, Fruit::Orange>, Ts&&... ts)
{
processOrange(std::forward<Ts>(ts)...);
}

template<typename... Ts, typename = decltype(processPear(std::forward<Ts>(std::declval<Ts>())...))>
static void process(std::integral_constant<Fruit, Fruit::Pear>, Ts&&... ts)
{
processPear(std::forward<Ts>(ts)...);
}

[[noreturn]] static void process(...)
{
// While this is inserted, it should never be called in practice,
// unless there's a programming error.
throw std::invalid_argument("Invalid argument specified to processFruit().");
}
};

template<typename... Args>
void processFruit(Fruit fruit_type, Args&&... args) {
switch (fruit_type) {
case Fruit::Apple:
Processor::process(std::integral_constant<Fruit, Fruit::Apple>(), std::forward<Args>(args)...);
break;
case Fruit::Orange:
Processor::process(std::integral_constant<Fruit, Fruit::Orange>(), std::forward<Args>(args)...);
break;
case Fruit::Pear:
Processor::process(std::integral_constant<Fruit, Fruit::Pear>(), std::forward<Args>(args)...);
break;
default:
break;
}
postProcessing();
return;
}

void processApple(int a, int b)
{
std::cout << "Apple: " << a << " // " << b << std::endl;
}

void processOrange(int c)
{
std::cout << "Orange: " << c << std::endl;
}

void processPear(double d)
{
std::cout << "Pear: " << d << std::endl;
}

void postProcessing()
{
std::cout << "Post processing" << std::endl;
}

int main()
{
processFruit(Fruit::Apple, 4, 8);
processFruit(Fruit::Orange, 15);
processFruit(Fruit::Pear, 1.234);
// The following will only throw an exception, but not fail to compile
//processFruit(Fruit::Pear, 1.234, 77);
return 0;
}

(The struct Processor is just to clean up the namespace, they could be global functions.) Problem here is that the compiler can't detect a wrong call, see the commented out call to processFruit(Fruit::Pear, 1.234, 77); that would generate an exception, but the compiler couldn't detect it at compile time.

In my eyes this is not very sensible though. Since the arguments depend on the Fruit type anyway, I don't really see how you could even perform a call to one of these functions when the Fruit argument is only known at runtime, because each call site will only work for a single type.

And if you do know that at compile time anyway, this could be done in a much simpler way via overloads, that also allows for much better compile-time diagnostics:

#include <iostream>
#include <type_traits>

enum class Fruit {
Apple,
Orange,
Pear,
};

void processApple(int a, int b);
void processOrange(int c);
void processPear(double d);
void postProcessing();

template<typename... Args>
void processFruit2Helper(std::integral_constant<Fruit, Fruit::Apple>, Args&&... args)
{
processApple(std::forward<Args>(args)...);
// or put the code of processApple directly in here
}

template<typename... Args>
void processFruit2Helper(std::integral_constant<Fruit, Fruit::Orange>, Args&&... args)
{
processOrange(std::forward<Args>(args)...);
// or put the code of processOrange directly in here
}

template<typename... Args>
void processFruit2Helper(std::integral_constant<Fruit, Fruit::Pear>, Args&&... args)
{
processPear(std::forward<Args>(args)...);
// or put the code of processPear directly in here
}

template<Fruit f, typename... Args>
void processFruit2(Args&&... args)
{
processFruit2Helper(std::integral_constant<Fruit, f>(), std::forward<Args>(args)...);
postProcessing();
}

void processApple(int a, int b)
{
std::cout << "Apple: " << a << " // " << b << std::endl;
}

void processOrange(int c)
{
std::cout << "Orange: " << c << std::endl;
}

void processPear(double d)
{
std::cout << "Pear: " << d << std::endl;
}

void postProcessing()
{
std::cout << "Post processing" << std::endl;
}

int main()
{
processFruit2<Fruit::Apple>(4, 8);
processFruit2<Fruit::Orange>(15);
processFruit2<Fruit::Pear>(1.234);

// The following will fail to compile here (which is good)
//processFruit2<Fruit::Pear>(1.234, 77);
return 0;
}

That all said, I suspect there's some higher-level design problem in your code, but we won't be able to understand that with the limited example you've provided.



Related Topics



Leave a reply



Submit