问题描述
我有一个模板类,在该类中我有一个带有通用/转发参考参数的静态模板函数。这个想法是完善函数的前向参数。
#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);
}
这一切正常,输出符合预期:
const T&
T&&
T&
但是,如果我要像这样更改 foo()
函数:
template <class... T>
inline void foo(T&&... args) {
FooClass<T...>::impl(std::forward<T>(args)...);
}
请注意,FooClass
类没有获得转发引用。然后输出是:
const T&
T
T&
即使我使用了 impl()
,右值引用的值类别也不会传递给函数 std::forward
。为什么会发生这种情况?是不是因为我没有在可推论的上下文中调用 impl()
,因为已经为 FooClass
推导出了 T?如何避免这样的问题?
编辑
我将 FooClass<T>::impl()
更改为模板函数。然后我发现 else if constexpr (std::is_rvalue_reference<T>::value)
从来都不是真的。我 discovered 这是因为右值被推导出为 T
类型而不是 T&&
类型。所以我添加了一些打印功能,发现完美转发现在可以工作了:
#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);
}
输出:
constant lvalue reference
rvalue reference
lvalue reference
解决方法
template <typename T>
struct FooClass<T> {
inline static void impl(T&& b) {
FooClass<X>::impl
需要一个 X&&
,其中 T
是 X
。
如果 X
为 int&&
,则 T&&
为 int&& &&
,即 int&&
,T
为 int&&
。>
如果X
为int
,则T&&
为int&&
,即int&&
,T
为int
。
在 impl
中,您查询的是 T
,而不是 b
。因此,如果您传递 FooClass
一个 int
or 和 int&&
,即使 FooClass<X>::impl
的签名是相同的,您也会得到不同的值。
调用 forward
之外的 impl
对 impl
中的代码具有零影响;要么调用成功,要么不成功,你会得到一个编译错误。
您不是推断 impl
的参数,而是明确设置类型。
forward
只不过是一个有条件的 move
。这不是魔法。
可能您需要重新了解 std 如何转发、模板函数参数推导(尤其是推导 T&&
参数时)以及引用折叠的工作原理。如果您不明白这一点,您可能会将 std forward 视为某种魔法,而实际上并非如此。