1

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

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

我声明了一个转发引用,而不是一个右值引用,这意味着:

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

可能不会得到我想要的。

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

4

4 回答 4

10

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

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 lvalue:只有第一个是可行的
  • 非常量,右值:两者都可行,第二个更好
  • const rvalue:两者都可行,第二个更专业更好的匹配
于 2017-10-25T15:16:31.303 回答
3

标签调度是最简单的解决方案。

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 是严重的过度杀伤力。

于 2017-10-25T19:22:14.267 回答
2

if constexpr您可以为 c++11 或c++17使用标签调度。类似的东西。

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
}
于 2017-10-25T15:10:17.507 回答
0

自从这个问题以来,情况发生了变化,因为 C++17 的情况要好一些:)(取决于你喜欢这种模式的方式)。

这可以在没有任何有趣的业务的情况下实现,std::enable_if但是,它只是将有趣的业务放在了别处,但我更喜欢它:

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

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

缺点是,您需要使用大括号而不是括号来运行它。

当然,还有一个可以玩的链接: 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

于 2020-06-29T12:34:28.537 回答