如何使用模板推导类型为右值和左值引用获取不同的重载?

How to get different overloads for rvalue and lvalue references with a template-deduced type?

我有一个函数 foo 通过引用获取参数,我希望它对右值和左值引用采取不同的操作。 (我还应该提到 foo() 尊重常量;它不会改变引用的值。)我知道如果我写:

template <typename T> foo(T&& x);

我声明了一个 forwarding reference,而不是一个右值引用,意思是这样的:

template <typename T> foo(const T& x);
template <typename T> foo(T&& x);

可能得不到我想要的。

所以,我的问题是:影响两种引用之间不同行为的正确方法是什么?

您可以为 c++11 使用标签调度或为 c++17 使用 if constexpr。诸如此类。

template <typename T> 
void foo(T&& x)
{
    foo(std::addressof(x), typename std::is_lvalue_reference<T>::type{});
}
void foo(void const *value, std::true_type)
{
   // do something on lvalue
}
void foo(void const *value, std::false_type)
{
   // do something on rvalue
}

您需要保存有关该类型的信息,尽管稍后 reinterpret_cast 该指针。或者更好的建议:

template <typename T> 
void foo(T&& x)
{
    foo(std::forward<T>(x), typename std::is_lvalue_reference<T>::type{});
}
template <typename T>
void foo(T &&value, std::true_type)
{
   // do something on lvalue
}
template <typename T>
void foo(T &&value, std::false_type)
{
   // do something on rvalue
}

您可以有一个左值引用重载和一个转发引用重载:

template <typename T> void foo(T& ) { ... }
template <typename T> void foo(T&& ) { ... }

对于左值,首选第一个重载。对于右值,只有第二个重载是可行的。


如果你想要的是一个 const 左值引用重载和一个非 const 右值引用重载,那么你只需要在转发引用的情况下添加一个约束:

template <typename T> void foo(T const& ) { ... }
template <typename T, REQUIRES(!std::is_reference<T>::value)>
void foo(T&& ) { ... }

其中 REQUIRES 是您选择的方法。现在,对于我们的四个案例:

  • 非常量,左值:只有第一个可行
  • const 左值:只有第一个可行
  • 非常量,右值:两者都可行,第二个更好匹配
  • const 右值:都可行,第 2 个 更专业 更好匹配

代码调度是最简单的解决方案。

namespace details {
  template <typename T>
  void foo(std::true_type is_lvalue, const T& x) {
    std::cout << x << " is an lvalue\n";
  }
  template <typename T>
  void foo(std::false_type is_lvalue, T&& x) {
    std::cout << x << " is an rvalue\n";
  }
}
template <typename T>
void foo(T&& t) {
  return details::foo(
    typename std::is_lvalue_reference<T>::type{},
    std::forward<T>(t)
  );
}

当您实际上不想支持为重载解析目的选择的两个版本时,SFINAE 是严重的矫枉过正。

自从这个问题之后,情况发生了变化,因为 C++17 的情况有所好转 :)(取决于您喜欢这种模式的方式)。

这可以在没有任何搞笑业务的情况下实现 std::enable_if 然而,它只是把搞笑业务放在别处,但我更喜欢它:

template <typename T>
struct Fn
{
    Fn(T &&);
    Fn(T const & v);
};

这两个构造函数是模板类型的重载。如果您需要 return 一个值,只需将其存储在 class 中并添加一个转换运算符。编译器使用构造函数来推断 class 模板类型。

缺点是,您需要 运行 用大括号代替方括号。

当然,link 可以玩: https://godbolt.org/z/oNzURY


同样的模式也适用于 C++11,但是嗯,不是那么好:

template <typename T>
bool Foo(T && value)
{
    return Fn<typename std::decay<T>::type>(std::forward<T>(value));
}

https://godbolt.org/z/6yvf7W