Wrap a pack of forwarding references in a tuple - c++

I have a function like this
template <typename... Args> void foo(Args&&... args);
to which I need to add an extra parameter at the end with a default argument. Since the pack needs to come last, I'm thinking of changing the function to
template <typename... Args> void foo(std::tuple<Args&&...> args,
const std::string& name = {});
The question is, what is the best way to pass the arguments in a tuple.
My understanding is that in the std::tuple<Args&&...> the Args are not forwarding references anymore, but strictly rvalue references. How do I get the forwarding references behavior for args wrapped in a tuple, e.g. accept an std::forward_as_tuple and preserve the reference types of the individual tuple elements. Also, what's the best way to pass the tuple here,
std::tuple<Args&&...> args
or
const std::tuple<Args&&...>& args
or
std::tuple<Args&&...>&& args
?
And do I need to use std::forward on the tuple elements inside the function, or simply std::get them?

My understanding is that in the std::tuple<Args&&...> the Args are not forwarding references anymore
Correct.
but strictly rvalue references
Yes, unless Args are specified explicitly, in which case reference collapsing can turn them into lvalue references, i.e., foo<int&>(...) will result in Args&& -> int& && -> int&.
what is the best way to pass the arguments in a tuple.
That depends on the intended usage of foo. If you don't need to know what Args... exactly are, you can probably get away with:
template <typename Tuple>
void foo(Tuple&& args, const std::string& name = {});
In such a case, individual types are still accessible using std::tuple_element_t<N, std::decay_t<Tuple>>.
If you do want to know Args... inside foo (without any additional levels of abstraction), you probably want to deduce the exact types, without any referenceness:
template <typename.... Args>
void foo(std::tuple<Args...>&& args, const std::string& name = {});
Note that if someone uses std::forward_as_tuple with lvalues and rvalues inside, the value categories will be stored in Args and you can still forward those arguments using std::forward (std::forward is not limited to forwarding references only, think of it as a conditional cast).
Also, what's the best way to pass the tuple here
Probably Tuple&& as suggested earlier. If not, then again it depends on the usage. If you use const std::tuple<Args...>&, then by looking at the list of overloads for std::get, you'll see that the the value category and constness propagates to the return value of std::get (modulo reference collapsing). The same is with std::tuple<Args...>&&. Also, using the latter, you will have to use a tuple rvalue as an argument (foo(std::forward_as_tuple(...), ...) as opposed to foo(my_tuple, ...)).
An alternative solution would be to accept a parameter pack, and detect whether the last parameter is something that can be bound by const std::string& or not:
#include <string>
#include <utility>
#include <tuple>
#include <type_traits>
struct dummy {};
template <typename... Args>
void foo_impl(Args&&... args)
{
const std::string& s = std::get<sizeof...(Args) - 1>(std::forward_as_tuple(std::forward<Args>(args)...));
}
template <typename... Args>
auto foo(Args&&... args)
-> std::enable_if_t<std::is_constructible<std::string, std::tuple_element_t<sizeof...(Args), std::tuple<dummy, Args...>>>{}>
{
foo_impl(std::forward<Args>(args)...);
}
template <typename... Args>
auto foo(Args&&... args)
-> std::enable_if_t<!std::is_constructible<std::string, std::tuple_element_t<sizeof...(Args), std::tuple<dummy, Args...>>>{}>
{
foo_impl(std::forward<Args>(args)..., "default");
}
DEMO

Related

How to forward packed variadic arguments

I have a function that accepts variadic arguments packed into a tuple
template <class... Args>
void Bottom(tuple<Args&&...> t)
{
}
Is Args&& a forwarding reference? I.e. would the rules for reference collapsing apply or am I just appending && to every argument in the pack?
And say I want to call this function from a function that certainly gets forwarding references:
template <class... Args>
void Top(Args&&... args) {
// 1. Bottom<Pack...>();
// 2. Bottom<Pack&&...>();
}
which syntax would be best if I don't want to alter the arguments, 1 or 2?
EDIT
I'm only using tuple to showcase a class that packs my parameters. The actual packing classes vary in different levels of the call hierarchy. The idea of using using_fwd_as_tuple is cool as a resource to find what the library does at this case.
I'd say none. I'd use std::forward_as_tuple and let compiler do the deduction:
template <class... Args>
void Top(Args&&... args) {
Bottom(std::forward_as_tuple(args...));
}
No, tuple<Args&&...> t are not forwarding references. They can only be present as top-level arguments.
You are not appending anything, you are attempting to match the arguments. Such function only accepts tuples (by value) which contain r-value references.
Example
#include <tuple>
using namespace std;
template <class... Args>
void Bottom(tuple<Args&&...> t)
{
}
// Type your code here, or load an example.
int main(){
double var=0.0;
tuple<int,double&,char&&> tup1{1,var,'c'};
//#1
//Bottom(tup1);
tuple<int&&,double&&,char&&> tup2{1,0.0,'c'};
//#2
//Bottom(tup2);
//#3
Bottom(std::move(tup2));
}
Does not compile since the arguments cannot be matched.
Does not compile either. Eventhough the arguments do match, the tuple itself is passed by value, in this case by copy and the copy constructor is deleted in presence of r-value tuple members.
Moving is fine, this instantiates this template:
template<>
void Bottom<int, double, char>(std::tuple<int &&, double &&, char &&> t)
{
}

Create object using parameters pack in template

I'd like to create template function that would create object basing on template typename and parameters pack.
I created a function that is supposed to create object based on typename from template, and I would also like to pass parameters pack to that template i order to pass parameters to constructor. Is this correct?:
template<typename TComponent, typename... Args>
void CreateComponent(Args... args)
{
std::shared_ptr<TComponent> component = std::make_shared<TComponent>(args ...);
}
I also wanted to pass those parameters to another fucntion like this:
template<typename TComponent, typename... Args>
void AddComponent(Args... args)
{
m_world->AddComponent<TComponent, Args>(m_id, args...);
}
But compiler returns an error " 'args' parameter pack must be expanded in this context"
Is it even possible to achieve what I want to achieve ?
But compiler returns an error " 'args' parameter pack must be expanded in this context"
Yes: you've forgotten to expand the types
m_world->AddComponent<TComponent, Args...>(m_id, args...);
// ...................................^^^
As pointed by Jarod42, according to the circumstances, you could avoid to explicit the Args... expansion
m_world->AddComponent<TComponent>(m_id, args...);
// no more Args...
and let the compiler deduce the types through args... (but we should see the AddComponent() definition).
Anyway, I don't see errors in your CreateComponents() function but, as correctly says François Andrieux in a comment, you don't using perfect forwarding.
It's a too-great argument to explain in an answer but, this way, you're renouncing to move semantics advantages (that is: you, potentially, make some unnecessary copies).
The following is your CreateComponents() function enabling perfect forwarding
template <typename TComponent, typename ... Args>
void CreateComponent (Args && ... args)
{ // .....................^^ forwarding reference added
std::shared_ptr<TComponent> component
= std::make_shared<TComponent>(std::forward<Args>(args)...);
} // ..............................^^^^^^^^^^^^^^^^^^^^^^^^

How is a template function evaluated by a compiler?

Consider the following variadic template function:
template<typename T, typename... Args>
auto foo(Args... args) -> T[sizeof...(args)]
{
T t[sizeof...(args)];
// Maybe do some pack expansion/assignment
return t;
}
with the instantiation:
// total should be of type int[5];
auto total = foo(1,2,3,4,5);
I understand that this will not compile, due to the return type not being deducible, but I do not understand why it is not deducible.
Is there something about this function that the compiler does/can not know at compile time, or the order of parts of the function being evaluated, which prevents type deduction?
I suspect it is due to the calling of the function sizeof..., which I think evaluates at run-time. If so, is there a compile-time equivalent?
You cannot return built-in arrays by value. Use std::array instead.
Also, as it stands, you'll need to explicitly provide the type T as a template argument as it does not appear in the argument list. Thus the compiler has nothing to deduce it from.
Complete example:
#include <array>
template<typename T, typename... Args>
auto foo(Args... args) -> std::array<T, sizeof...(args)>
{
std::array<T, sizeof...(args)> t;
// Maybe do some pack expansion/assignment
return t;
}
int main () {
auto a = foo<int>(1,2,3);
}
Depending on the usecase, you may get rid of the explicit template argument by e.g. using the std::common_type of all elements in the pack or static_asserting they all have the same type and use that.
Also, for the record, sizeof... does yield a compile-time constant. The problem is, to make the answer explicit, that the compiler has no way tell what T is supposed to be.
Compiler unable to deduce T, because you don't pass T to function argument.
auto total = foo<int>(1,2,3,4,5);
If we correctly put T manually as template argument - it will compile & work fine on G++7.1
Or, you can simply decltype required type from variadic argument.
something like this:
template <typename T, typename...Args>
T getHead(T&& arg, Args&&...) {
return arg;
}
template<typename... Args>
auto foo(Args... args)
{
using TYPE = typename std::decay<decltype(getHead(args...))>::type;
return std::array<TYPE, sizeof...(Args)>{ args... };
}
And yes, use std::array, return local C-style array is undefined behaviour.

Variadic templates argument fowarding

Say I have a function foo() with takes advantage of c++ variadic templates feature. Now, what's the difference between these implementations:
template <typename... Args>
void foo(Args... args) {
whatever(args...);
}
template<typename... Args>
void foo(Args&... args) {
whatever(args...);
}
template<typename... Args>
void foo(Args... args) {
whatever(&args...);
}
template<typename... Args>
void foo(Args&&... args) {
whatever(std::forward<Args>(args)...);
}
template <typename... Args>
void foo(Args... args) {
whatever(args...);
}
foo gets copies of args and passes them to whatever as l-values.
template<typename... Args>
void foo(Args&... args) {
whatever(args...);
}
foo gets l-value references to args and passes them to whatever as l-values.
template<typename... Args>
void foo(Args... args) {
whatever(&args...);
}
foo gets copies of args and passes them to whatever as pointers to l-values. Be careful of object lifetimes here.
template<typename... Args>
void foo(Args&&... args) {
whatever(std::forward<Args>(args)...);
}
foo gets forwarding references to args. Whether they are l-values or r-values depends on what happens at the call site. They are then perfect-forwarded to whatever, preserving their reference type. Scott Meyers originally called these "universal" references, but forwarding reference is the preferred terminology now.
The first one takes its arguments by value. The second one takes its arguments by lvalue reference (so non-const rvalues cannot be used). The third one also takes its arguments by value, but passes pointers to whatever. The fourth one takes its arguments by forwarding reference and perfect-forwards them to whatever.
There is nothing magical about the variadic templates; the rules are the same as if there were only one argument.

std::apply may not be properly implemented

std::apply is mentioned in few stackoverflow answers and n3658, n3915 and usually defined as:
template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, index_sequence<I...>) {
return forward<F>(f)(get<I>(forward<Tuple>(t))...);
}
template <typename F, typename Tuple>
decltype(auto) apply(F&& f, Tuple&& t) {
using Indices = make_index_sequence<tuple_size<decay_t<Tuple>>::value>;
return apply_impl(forward<F>(f), forward<Tuple>(t), Indices{});
}
However the reference implementation std::apply function fails to compile in such context (tested with clang 3.8 and gcc 5.2):
std::apply ([] (int&) {} , std::make_tuple (42));
One possible workaround is simply to remove std::forward<Tuple> from apply_impl but leave the universal reference intact:
template <typename F, typename Tuple, size_t... I>
decltype(auto) apply_impl(F&& f, Tuple&& t, index_sequence<I...>) {
return forward<F>(f)(get<I>(t)...);
}
Is there any drawbacks of this workaround? Is there more convenient solution possible?
UPDATE: Another possible workaround without changing std::apply (inspired by this SO answer):
template <typename T> constexpr T& make_tmp (T&& t) noexcept { return t; }
...
std::apply ([] (int& i) {} , make_tmp (std::make_tuple (42)));
Is it correct and result well defined?
When you have a temporary tuple containing a temporary object (or a reference to a temporary object), a function that demands a writable reference is right to reject the application. The function expects callers to pay attention to what it writes, and you are deterministicly discarding it.
Here is code that compiles:
int main() {
int i = 42;
std::apply ([] (int&) {} , std::tie(i) );
}
make_tuple creates a tuple of copies. apply then invokes the passed-in f on the soon to be discarded copies: these are quite properly treated as rvalues.
If you have an object which you want to pass by-reference through std::apply, put a reference to it in the tuple, not a copy of it. Then the rvalue-ness of the tuple within apply does not apply to reference contents.
For std::get<N>(some_tuple) to return an rvalue, either:
(A) the nth element must be a copy, and the tuple must be an rvalue
(B) the nth element must be an rvalue reference, and the tuple must be an rvalue reference
By storing an lvalue reference, std::get will never return an rvalue reference.
You probably want to call std::tie or std::forward_as_tuple depending on the situation. std::make_tuple is for when you want to create a copy in the tuple, not when you are storing references to other objects.
There are ways to allow you to pass rvalues as lvalues when you do want to discard any modifications, but those are usually bad ideas, and they should be done in client code with big warning stickers all over the place, not implicitly in a library.
template<class T>
T& as_lvalue( T&& t ) { return t; }
is a simple one.