模板类中的完美转发

问题描述

我有一个模板类,在该类中我有一个带有通用/转发参考参数的静态模板函数。这个想法是完善函数的前向参数。

#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&

Demo

但是,如果我要像这样更改 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?如何避免这样的问题?

(这个问题与今天早些时候发布的另一个one有关)。

编辑

我将 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

Demo

解决方法

template <typename T>
struct FooClass<T> {
  inline static void impl(T&& b) {

FooClass<X>::impl 需要一个 X&&,其中 TX

如果 Xint&&,则 T&&int&& &&,即 int&&Tint&&。>

如果Xint,则T&&int&&,即int&&Tint

impl 中,您查询的是 T,而不是 b。因此,如果您传递 FooClass 一个 int or 和 int&&,即使 FooClass<X>::impl 的签名是相同的,您也会得到不同的值。

调用 forward 之外的 implimpl 中的代码具有零影响;要么调用成功,要么不成功,你会得到一个编译错误。

不是推断 impl 的参数,而是明确设置类型。

forward 只不过是一个有条件的 move。这不是魔法。

可能您需要重新了解 std 如何转发、模板函数参数推导(尤其是推导 T&& 参数时)以及引用折叠的工作原理。如果您不明白这一点,您可能会将 std forward 视为某种魔法,而实际上并非如此。