Suppose I have a template class
template <typename T> class foo;
template <typename... Args>
struct foo<std::tuple<Args...>> {
std::tuple<Args...> t;
foo(Args&&... args): t{std::forward<Args>(args)...} { }
};
I understand that in this case Args&&...
are rvalue references, and I could have just as well written std::move
instead of std::forward
.
I can also have a constructor with lvalue references, like so
foo(const Args&... args): t{args...} { }
The question is whether it's possible to get the same behavior as with forwarding references, but for definite types? The reason I want this is so I can used syntax like
foo bar({. . .}, {. . .}, . . ., {. . .});
This works if I define the foo(Args&&... args)
constructor, but doesn't allow for a mixed scenario, where I want to initialize some of the member tuple elements with brace-enclosed initializer lists and have others copied from preexisting object instances.
I understand that a forwarding reference is "an rvalue reference to a cv-unqualified template parameter", such as in. template <class T> void foo(T&& ); which means the above function can take both l-value and r-value reference.
A forwarding reference is an rvalue reference to a cv-unqualified template parameter. If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction. Hence, the two mean the same thing, and the current C++ standard term is forwarding reference.
What is Perfect Forwarding. Perfect forwarding allows a template function that accepts a set of arguments to forward these arguments to another function whilst retaining the lvalue or rvalue nature of the original function arguments.
std::forward has a single use case: to cast a templated function parameter (inside the function) to the value category (lvalue or rvalue) the caller used to pass it. This allows rvalue arguments to be passed on as rvalues, and lvalues to be passed on as lvalues, a scheme called “perfect forwarding.”
Sure; there is a fancy and simple way.
The fancy way I will detail below. First the simple way: take by value.
template <typename... Args>
struct foo<std::tuple<Args...>> {
std::tuple<Args...> t;
foo(Args... args): t{std::forward<Args>(args)...} { }
};
Really, just do this. Forward is used to do the right thing if Args
contains a reference.
Taking by value adds one move over perfect forwarding, but reduces the requirement for overloads exponentially.
This is the fancy way. We type erase construction:
template<class T>
struct make_it {
using maker=T(*)(void*);
maker f;
void* args;
// make from move
make_it( T&& t ):
f([](void* pvoid)->T{
return std::move(*static_cast<T*>(pvoid));
}),
args(std::addressof(t))
{}
// make from copy
make_it( T const& t ):
f([](void* pvoid)->T{
return *(T const*)(pvoid);
}),
args(std::addressof(t))
{}
operator T()&&{return std::move(*this)();}
T operator()()&&{ return f(args); }
};
This type erases construction by copy or move.
template <typename... Args>
struct foo<std::tuple<Args...>> {
std::tuple<Args...> t;
foo(make_it<Args>... args): t{std::move(args)()...} { }
};
It isn't perfectly transparent, but it is as close as I can get.
Double {{}}
is required instead of single. It is a user defined conversion, so another one will not implicitly be done. We could add a universal ctor:'
// make from universal
template<class U>
make_it( U&& u ):
f([](void* pvoid)->T{
return std::forward<U>(*(U*)(pvoid));
}),
args(std::addressof(u))
{}
which works better if we add a sfinae teat that U&&
can be used to implicitly construct T
.
It has a few advantages, but they are marginal over just taking by value. For example, in C++17 non-movable types can be perfect forward constructed in some cases.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With