I have a template class and within that class I have a static template function with universal/forwarding reference parameters. The idea is to perfect forward arguments to the function.
#include <iostream>
#include <type_traits>
//Type traits
template <typename T>
struct is_lreference_const
{
static const bool value = std::is_lvalue_reference<T>::value && std::is_const<typename std::remove_reference<T>::type>::value;
};
template <typename T>
struct is_lvreference
{
static const bool value = std::is_lvalue_reference<T>::value && !std::is_const<typename std::remove_reference<T>::type>::value;
};
struct Bar{};
template <class... Args>
struct FooClass;
//Perfect forward to FooClass::impl()
template <class... T>
inline void foo(T&&... args) {
FooClass<T&&...>::impl(std::forward<T>(args)...);
}
template <typename T>
struct FooClass<T> {
inline static void impl(T&& b) {
if constexpr (is_lvreference<T>::value)
std::cout << "T&" << std::endl;
else if constexpr (is_lreference_const<T>::value)
std::cout << "const T&" << std::endl;
else if constexpr (std::is_rvalue_reference<T>::value)
std::cout << "T&&" << std::endl;
else
std::cout << "T" << std::endl;
}
};
int main()
{
const Bar b2;
foo(b2);
foo(Bar{});
Bar b;
foo(b);
}
This all works fine and the output is as expected:
const T&
T&&
T&
However, if I was to change the foo()
function like this:
template <class... T>
inline void foo(T&&... args) {
FooClass<T...>::impl(std::forward<T>(args)...);
}
Notice that the FooClass
class doesn't get a forwarding reference. Then the output is:
const T&
T
T&
The value category for the rvalue reference is not passed to the function impl()
even though I'm using std::forward
. Why does this happen? Is it because I'm not calling impl()
in a deducible context as T has already been deduced for FooClass
? How to avoid such an issue?
(This question is related to another one posted earlier today).
I changed FooClass<T>::impl()
to be a template function. Then I found that else if constexpr (std::is_rvalue_reference<T>::value)
was never true. I discovered ths was because rvalues are deduced to be of type T
and not T&&
. So I added some print functions and found that perfect forwarding now worked:
#include <iostream>
#include <type_traits>
//Type traits
template <typename T>
struct is_lreference_const
{
static const bool value = std::is_lvalue_reference<T>::value && std::is_const<typename std::remove_reference<T>::type>::value;
};
template <typename T>
struct is_lvreference
{
static const bool value = std::is_lvalue_reference<T>::value && !std::is_const<typename std::remove_reference<T>::type>::value;
};
struct Bar{};
template <class... Args>
struct FooClass;
//Perfect forward to FooClass::impl()
template <class... T>
inline void foo(T&&... args) {
FooClass<T&&...>::impl(std::forward<T>(args)...);
}
template<typename T>
void printme(const T&) {
std::cout << "constant lvalue reference" << std::endl;
}
template<typename T>
void printme(T&) {
std::cout << "lvalue reference" << std::endl;
}
template<typename T>
void printme(T&&) {
std::cout << "rvalue reference" << std::endl;
}
template <typename T>
struct FooClass<T> {
template <typename Arg>
inline static void impl(Arg&& b) {
printme(std::forward<Arg>(b));
}
};
int main()
{
const Bar b2;
foo(b2);
foo(Bar{});
Bar b;
foo(b);
}
Output:
constant lvalue reference
rvalue reference
lvalue reference
template <typename T>
struct FooClass<T> {
inline static void impl(T&& b) {
FooClass<X>::impl
takes an X&&
, and in it T
is X
.
If X
is int&&
, then T&&
is int&& &&
which is int&&
and T
is int&&
.
If X
is int
, then T&&
is int&&
which is int&&
, and T
is int
.
Within impl
, you query T
, not b
. So you get a different value if you pass FooClass
an int
or and int&&
, even though the signature of FooClass<X>::impl
is the same.
The forward
outside of the call to impl
has zero impact on the code within impl
; either the call succeeds, or it does not and you get a compilation error.
You are not deducing the arguments to impl
, you are explicitly setting the types.
forward
is nothing but a conditional move
. It isn't magic.
Probably you need to brush up on how std forward, template function argument deduction (especially when you deduce T&&
parameters), and how reference collapsing works. If you don't understand that, you might treat std forward as some kind of magic, when it isn't.