Consider the following code:
#include <iostream>
#include <tuple>
#include <utility>
// A.
template <typename... Args>
void f (const char* msg, Args&&... args)
{
std::cout << "A. " << msg << "\n";
}
// B.
template <typename... Args>
void f (const char* msg, std::tuple<Args...>&& t)
{
std::cout << "B. " << msg << "\n";
}
struct boo
{
const std::tuple<int, int, long> g () const
{
return std::make_tuple(2, 4, 12345);
}
};
int main ()
{
f("First", 2, 5, 12345);
f("Second", std::make_tuple(2, 5, 12345));
boo the_boo;
f("Third", the_boo.g());
f("Fourth", std::forward<decltype(std::declval<boo>().g())>(the_boo.g()));
return 0;
}
The output of that will be:
A. First
B. Second
A. Third
A. Fourth
From the output it's evident that it does not do what I would like it to do, that is I would like Third and Fourth to go through the B. version of the function. The std::forward from the Fourth call is superfluous as perfect forwarding does not happen there. In order to have perfect forwarding I know:
I understand it does not work. But I do not fully grasp:
why the context is changed by using std::tuple in such a way that it fails to work as desired ? Why the template parameter cannot be the type for another templated type?
how can I(elegantly) fix it ?
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 If arg is an lvalue reference, the function returns arg without modifying its type. This is a helper function to allow perfect forwarding of arguments taken as rvalue references to deduced types, preserving any potential move semantics involved.
std::move takes an object and casts it as an rvalue reference, which indicates that resources can be "stolen" from this object. std::forward has a single use-case: to cast a templated function parameter of type forwarding reference ( T&& ) to the value category ( lvalue or rvalue ) the caller used to pass it.
Class template std::tuple is a fixed-size collection of heterogeneous values. It is a generalization of std::pair.
Your issue is that in Third and Fourth you are passing a const std::tuple
where B. expects a non-const version.
When the compiler attempts to generate code for the call to f
, it sees that you are calling with a const std::tuple
and so deduces the type of Args...
to be const std::tuple
. Calling B. is not valid because the variable has a different const-qualification than expected.
To solve this, just make g()
return a non-const tuple.
Edit:
In order for perfect forwarding to occur, you need a deduced context, as you say in your question. When you say std::tuple<Args...>&&
in the function argument list, Args...
is deduced, but std::tuple<Args...>&&
is not; it can only by an rvalue reference. In order to fix this, that argument needs to be of the form T&&
where T
is deduced.
We can accomplish this using a custom type trait:
template <typename T>
struct is_tuple : std::false_type {};
template <typename... Args>
struct is_tuple <std::tuple<Args...>> : std::true_type {};
Then we use this trait to enable a single-argument template for tuples only:
// B.
template <typename T, typename = typename std::enable_if<
is_tuple<typename std::decay<T>::type>::value
>::type>
void f (const char* msg, T&& t)
{
std::cout << "B. " << msg << "\n";
std::cout << "B. is lval == " << std::is_lvalue_reference<T>() << "\n";
}
Or alternatively:
//! Tests if T is a specialization of Template
template <typename T, template <typename...> class Template>
struct is_specialization_of : std::false_type {};
template <template <typename...> class Template, typename... Args>
struct is_specialization_of<Template<Args...>, Template> : std::true_type {};
template <typename T>
using is_tuple = is_specialization_of<T, std::tuple>;
is_specialization_of taken from here and suggested by this question.
Now we have perfect forwarding!
int main ()
{
f("First", 2, 5, 12345);
f("Second", std::make_tuple(2, 5, 12345));
boo the_boo;
f("Third", the_boo.g());
f("Fourth", std::forward<decltype(std::declval<boo>().g())>(the_boo.g()));
auto the_g = the_boo.g();
f("Fifth", the_g);
return 0;
}
Outputs:
A. First
B. Second
B. is lval == 0
B. Third
B. is lval == 0
B. Fourth
B. is lval == 0
B. Fifth
B. is lval == 1
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