【问题标题】:What is the proper way to declare a function that can take either rvalue or lvalue reference?声明可以采用右值或左值引用的函数的正确方法是什么?
【发布时间】:2017-02-06 01:46:58
【问题描述】:

假设我想要一个函数foo 以左值或右值引用作为参数。

我可以将它分成两个重载,分别采用左值和右值引用。

void foo(int& a){/*some impl*/} // lvalue ref
void foo(int&& a){foo(a);} // rvalue ref
int main(){
  int a;
  foo(a); // lvalue
  foo(1); // rvalue
}

它确实有效,但非常冗长。我可以使用模板对其进行改进。

template<typename T>
void foo(T &&a) { /*some impl*/ }
int main(){
  int a;
  foo(a); // lvalue, T = int&
  foo(1); // rvalue, T = int
}

对于二元函数,模板需要采用两个模板参数。

template<typename T1, typename T2>
void foo(T1 &&a, T2 &&b) { /*some impl*/ }
int main(){
  int a;
  foo(a, a); // (lvalue, lvalue), T1 = int&, T2 = int&
  foo(1, 1); // (rvalue, rvalue), T1 = int,  T2 = int
  foo(1, a); // (rvalue, lvalue), T1 = int,  T2 = int&
  foo(a, 1); // (lvalue, rvalue), T1 = int&, T2 = int
}

这是要走的路吗?有没有更好的办法?

我几乎没有任何使用 cpp 的经验,但我需要做这些技巧来简单地说“不要复制传递的参数”,这似乎很可疑。

我正在使用 gcc 5.4.0-std=c++11

-- 更新 1--

我在使用streamsrange(T&amp;&amp; lower, T&amp;&amp; upper) 方法时提出了这个问题,该方法同时采用T&amp;&amp; 参数。我可以将lvaluervalue 参数都传递给函数,但这使我无法将0some_var 作为参数传递。无论作者为什么函数采用T&amp;&amp; 参数,我想知道是否有一种方法可以扩展声明以采用混合的左值/右值参数而不牺牲作者想要实现的任何目标。

-- 更新 2--

如果参数是只读的const &amp;可以使用(@RichardCritten)。

当您想修改/返回参数而不复制时,您可以使用模板或@Yakk 解决方案。

@Yakk 的解决方案在您声明一个接受多个参数的函数时似乎更好。

例如,如果一个函数采用两个 int l/r-value 引用参数并使用模板返回 int 引用会导致签名混乱。

template<typename T1, typename T2>
int& foo(T1 &&a, T2 &&b) {
  a += b;
  return a;
}

虽然@Yakk 解决方案提供了一个非常优雅的解决方案。

int& foo(any_ref<int> a, any_ref<int> b) {
  a += b;
  return a;
}

【问题讨论】:

  • 练习的意义何在?为什么你不能让一个函数以老式的方式通过值或通过 const 引用来获取参数?
  • 如果您不需要更改参数的值,只需使用T const &amp;
  • 这个问题通常与一个XY问题有关,也许你可以更详细地描述一下导致你问这个问题的情况
  • @IgorTandetnik @M.M 我在使用streamsrange(T&amp;&amp; lower, T&amp;&amp; upper) 方法时提出了这个问题,该方法同时采用右值引用参数,这使我无法通过例如0 和@987654346 @ 作为参数。
  • 您链接到的基本原理的代码使用通用引用。这与您关于int&amp;&amp; 的问题的实际内容非常不同,int&amp;&amp; 不是通用参考。 T&amp;&amp;(其中 T 是模板参数)绑定到左值,但 int&amp;&amp; 没有。更新中的内容应该是之前所有内容的单独问题(即如何修改此range 函数模板以接受(0, some_var)

标签: c++ c++11


【解决方案1】:
template<class T>
struct any_ref{
  T& t;
  any_ref(T&in):t(in){}
  any_ref(T&&in):any_ref(in){}
  any_ref(any_ref const&)=default;
  any_ref& operator=(any_ref const&)=default;
  ~any_ref()=default;

  operator T&()const&{return t;}
  operator T&&()&&{return std::move(t);}// maybe
  T& get()const{return *this;}
};

然后:

void foo(any_ref<int>a, any_ref<int>b);

不是模板函数。如果需要真正的引用,主体可以执行int&amp;a=a_arg;

但实际上,如果您想要一个“可能退出”参数,只需使用 int&amp;。想要将右值作为左值传递的调用者可以这样写:

templare<class T>
T& as_lvalue(T&&t){return t;}

并抛弃它们的右值性。

如果参数是只读引用,请使用const&amp;

【讨论】:

  • 如果你能摆脱get函数就好了。不管怎样,它做了我想做的一切。我想知道为什么标准没有定义合适的工具来做到这一点。
  • @MarcinKról 因为as_lvalue 可能是一种更好的方法——在调用站点将右值标记为左值。您希望有一个可以修改的引用参数应该绑定到一个临时的,这是非常罕见的。我很少看到有一个 in 参数和一个不同的(可忽略的)out 参数(作为返回值的一部分)会更好的情况。我见过,但很少见,在这些罕见的情况下,as_lvalue 可以。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多