Why Is Forwarding Variadic Parameters Invalid

Why is forwarding variadic parameters invalid?

When you call foo, the compiler expects a series of arguments, each of which must be an Int.

In the body of foo2, bar2 summarises all the passed arguments and actually has the type Int[] for all practical purposes. Thus, you cannot pass it directly to foo — as foo wants Int arguments, and not an Int[].

As for a solution to this: see my answer to this question.

Perfect forwarding class variadic parameters

The easiest way to solve the problem is simply to take a pack of values, and move from them:

template<class...Ts>
struct A{
void method(Ts...ts){
// some code
std::vector<std::tuple<Ts...>> vec;
vec.emplace_back(std::forward_as_tuple(std::move(ts)...));
// some other code
}
};

the above doesn't behave well if Ts contain references, but neither did your original code. It also forces a redundant move, which for some types is expensive. Finally, if you didn't have a backing vec, it forces your types to be moveable -- the solutions below do not.

This is by far the simplest solution to your problem, but it doesn't actually perfect forward.


Here is a more complex solution. We start with a bit of metaprogramming.

types is a bundle of types:

template<class...>struct types{using type=types;};

conditional_t is a C++14 alias template to make other code cleaner:

// not needed in C++14, use `std::conditional_t`
template<bool b, class lhs, class rhs>
using conditional_t = typename std::conditional<b,lhs,rhs>::type;

zip_test takes one test template, and two lists of types. It tests each element of lhs against the corresponding element of rhs in turn. If all pass, it is true_type, otherwise false_type. If the lists don't match in length, it fails to compile:

template<template<class...>class test, class lhs, class rhs>
struct zip_test; // fail to compile, instead of returning false

template<
template<class...>class test,
class L0, class...lhs,
class R0, class...rhs
>
struct zip_test<test, types<L0,lhs...>, types<R0,rhs...>> :
conditional_t<
test<L0,R0>{},
zip_test<test, types<lhs...>, types<rhs...>>,
std::false_type
>
{};

template<template<class...>class test>
struct zip_test<test, types<>, types<>> :
std::true_type
{};

now we use this on your class:

// also not needed in C++14:
template<bool b, class T=void>
using enable_if_t=typename std::enable_if<b,T>::type;
template<class T>
using decay_t=typename std::decay<T>::type;

template<class...Ts>
struct A{
template<class...Xs>
enable_if_t<zip_test<
std::is_same,
types< decay_t<Xs>... >,
types< Ts... >
>{}> method(Xs&&... plist){
// some code
std::vector<std::tuple<Tlist...>> vec;
vec.emplace_back(
std::forward_as_tuple(std::forward<Xlist>(plist)...)
);
// some other code
}
};

which restricts the Xs to be exactly the same as the Ts. Now we probably want something slightly different:

  template<class...Xs>
enable_if_t<zip_test<
std::is_convertible,
types< Xs&&... >,
types< Ts... >
>{}> method(Xs&&... plist){

where we test if the incoming arguments can be converted into the data stored.

I made another change forward_as_tuple instead of make_tuple, and emplace instead of push, both of which are required to make the perfect forwarding go all the way down.

Apologies for any typos in the above code.

Note that in C++1z, we can do without zip_test and just have a direct expansion of the test within the enable_if by using fold expressions.

Maybe we can do the same in C++11 using std::all_of and constexpr initializer_list<bool>, but I haven't tried.

zip in this context refers to zipping up to lists of the same length, so we pair up elements in order from one to the other.

A significant downside to this design is that it doesn't support anonymous {} construction of arguments, while the first design does. There are other problems, which are the usual failurs of perfect forwarding.

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 an invocation of a variadic function in C

If you don't have a function analogous to vfprintf that takes a va_list instead of a variable number of arguments, you can't do it. See http://c-faq.com/varargs/handoff.html.

Example:

void myfun(const char *fmt, va_list argp) {
vfprintf(stderr, fmt, argp);
}

When to use std::forward to forward arguments?

Use it like your first example:

template <typename T> void f(T && x)
{
g(std::forward<T>(x));
}

template <typename ...Args> void f(Args && ...args)
{
g(std::forward<Args>(args)...);
}

That's because of the reference collapsing rules: If T = U&, then T&& = U&, but if T = U&&, then T&& = U&&, so you always end up with the correct type inside the function body. Finally, you need forward to turn the lvalue-turned x (because it has a name now!) back into an rvalue reference if it was one initially.

You should not forward something more than once however, because that usually does not make sense: Forwarding means that you're potentially moving the argument all the way through to the final caller, and once it's moved it's gone, so you cannot then use it again (in the way you probably meant to).

Forwarding variadic function parameters to a std::function object

Your queue is holding references to the arguments so the argument must still be in scope when the function is called. e.g.

{
int value = 1;
test.push(func2, value);
}
test.front()(); //Invalid, value is out of scope

int value2 = 2;
test.push(func2, value2);
test.front()(); //Ok, value2 is still in scope

test.push(func2, 3);
test.front()(); //Invalid, the temporary that was holding 3 is out of scope

If you want the function to always be valid you will need to store the arguments by value. Capturing parameter packs by value in a lambda isn't straight forward, however, we can use std::bind instead of a lambda.

#include <functional>
#include <queue>
#include <iostream>

class thread_queue {

public:
thread_queue() = default;
void push(std::function<void()> func) { thread_q_.push(func); }

template <typename Ret, typename ... Params>
void push(Ret (&func)(Params...), Params&&... params) {
std::function<void()> temp = std::bind(func, std::forward<Params>(params)...);
push(std::move(temp));
}

void pop() { thread_q_.pop(); }

std::function<void()> front() { return thread_q_.front(); } //could avoid a copy
//by returning a reference. Would be more consistent with other containers.

private:
std::queue<std::function<void()>> thread_q_;

};

void func1() {
std::cout << "Inside of func1" << std::endl;
}

void func2(int a) {
std::cout << "Inside of func2 " << a << std::endl;
}

int func3() {
std::cout << "Inside of func3" << std::endl;
return 5;
}

int func4(int a, int b) {
std::cout << "Inside of func4 " << a + b << std::endl;
return a + b;
}

int main() {

thread_queue test;
test.push(func1);
test.push(func2, 10);
test.push(func3);
test.push(func4, 1, 8);

test.front()();
test.pop();
test.front()();
test.pop();
test.front()();
test.pop();
test.front()();
test.pop();


return 0;
}

UPDATE:
If you have move only parameters std::bind will not work as the object it returns can be called multiple times and thus can't move the stored parameters. Another problem with move only parameters is that std::function requires the function object passed to it to be copyable. One why of solving these problems is to store a std::shared_ptr in the std::function e.g.

#include <functional>
#include <queue>
#include <iostream>
#include <tuple>
#include <memory>

class thread_queue {
template <typename Ret, typename... Params>
struct callable {
Ret (&func)(Params...);
std::tuple<Params...> params;

template<typename... Params2>
callable(Ret (&func1)(Params...), Params2&&... params) :
func(func1),
params{std::forward<Params2>(params)...}
{}

void operator()() {
std::apply(func, std::move(params));
}
};
public:
thread_queue() = default;
void push(std::function<void()> func) { thread_q_.push(std::move(func)); }

template <typename Ret, typename... Params>
void push(Ret (&func)(Params...), Params&&... params) {
auto data = std::make_shared<callable<Ret, Params...>>(func, std::forward<Params>(params)...);
thread_q_.push(std::function<void()>{
[data = std::move(data)]() {
(*data)();
}
});
}

void pop() { thread_q_.pop(); }

std::function<void()>& front() { return thread_q_.front(); }
private:
std::queue<std::function<void()>> thread_q_;

};

struct MoveOnly {
MoveOnly() {}
MoveOnly(MoveOnly&&) {}
};

void func5(MoveOnly m) {
std::cout << "func5\n";
}

int main() {
thread_queue test;
test.push(func5, MoveOnly{});
test.front()();
test.pop();

return 0;
}

Another likely faster solution is to write your own version of std::function. The following is a minimal example of this and doesn't include small buffer optimization.

#include <functional>
#include <queue>
#include <iostream>
#include <tuple>
#include <memory>

template<class T>
class move_only_function;

template<class R, class... Args>
class move_only_function<R(Args...)>
{
struct base_callable
{
virtual R operator()(Args... args) = 0;
virtual ~base_callable() = default;
};

template<class F>
struct callable : public base_callable
{
F func;
callable(const F& f) : func(f) {}
callable(F&& f) : func(std::move(f)) {}

virtual R operator()(Args... args) override
{
return static_cast<R>(func(args...));
}
};

std::unique_ptr<base_callable> func;
public:
move_only_function(move_only_function&& other) : func(std::move(other.func)) {}

template<class F>
move_only_function(F&& f) : func(std::make_unique<callable<F>>(std::forward<F>(f))) {}

template<class... Args2>
R operator()(Args2&&... args)
{
return (*func)(std::forward<Args2>(args)...);
}
};

class thread_queue {
public:
thread_queue() = default;
void push(move_only_function<void()> func) { thread_q_.push(std::move(func)); }

template <typename Ret, typename ... Params>
void push(Ret (&func)(Params...), Params&&... params) {
thread_q_.push(move_only_function<void()>{
[func, tup=std::make_tuple(std::forward<Params>(params)...)]() mutable {
return std::apply(func, std::move(tup));
}});
}

void pop() { thread_q_.pop(); }

move_only_function<void()>& front() { return thread_q_.front(); }
private:
std::queue<move_only_function<void()>> thread_q_;

};

struct MoveOnly {
MoveOnly() {}
MoveOnly(MoveOnly&&) {}
};

void func5(MoveOnly m) {
std::cout << "func5\n";
}

int main() {
thread_queue test;
test.push(func5, MoveOnly{});
test.front()();
test.pop();

return 0;
}

Perfect-forwaring of the variadic template parameters of a struct

In your first example :

template <typename... T>
struct S
{
void X(T&&... args)
{
Do(std::forward<T>(args)...);
}
};

T is evaluated to V when you declare S<V> s, therefore the only prototype of the generated X member function is :

void X(V&&);

Whereas in your second example :

template <typename... T>
struct S
{
template <typename... T2>
void X(T2&&... args)
{
Do(std::forward<T2>(args)...);
}
};

T is evaluated to V& when you call it with s.X(v) so the generated prototype of the member function X is :

void X(V& &&);

which becomes (with reference colapsing) :

void X(V&);

To answer your second question, you either need to repeat the variadic template parameters or use function overloading.

How to add a parameter value when forwarding parameters to a variadic template function?


How to add hw to the forward list?

Simply

Bar<T>(hw, std::forward<Args>(args)...); 
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

or if you want to move the hw to Bar()

#include <utility>      // std::move, std::forward

Bar<T>(std::move(hw), std::forward<Args>(args)...);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

or let the compiler deduce the type T

Bar(std::move(hw), std::forward<Args>(args)...); 
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

for that, the Bar does not required the first template argument T

template<typename... Args>
void Bar(Args&&... args)
{
// ....
}

That being said, you might want to change the normal if statement with if constexpr for compile time branching, as follows:

#include <utility>      // std::move, std::forward
#include <type_traits> // std::is_same_v

template<typename T, typename... Args>
void Foo(Args&&... args)
{
if constexpr (std::is_same_v<T, std::string>)
{
std::string hw = "Hello, world!";
Bar(std::move(hw), std::forward<Args>(args)...);
}
else
{
Bar(std::forward<Args>(args)...);
}
}

Here is the complete demo



Related Topics



Leave a reply



Submit