将多个右值和左值传递给函数而不创建重载

passing multiple rvalues and lvalues to a function without creating overloads

假设您正在为客户端库提供一个具有多个引用参数的函数。

为了简单起见,假设我们有 3 个参数,它们是对 int 的各种引用(因此您可以假设,这些常量也将由 initializer_list 创建)。

从调用站点应该可以将临时创建的常量(右值)传递给函数(在这种情况下考虑测试代码)以及对另一个实体拥有的对象的真实引用。

到目前为止,我想出了以下解决方案:

void bar(int &x, int &y, int &z) { }

// use a template wrapper to turn rvalues into lvalues.
template <typename T, typename U, typename V>
void foo(T &&t, U &&u, V &&v) { 
    bar(t,u,v);
}

// create a humongous amount of overloads
void baz(int &&x, int &y, int &z) { }
void baz(int &x, int &&y, int &z) { }
void baz(int &&x, int &&y, int &z) { }
void baz(int &x, int &y, int &&z) { }
void baz(int &&x, int &y, int &&z) { }
void baz(int &x, int &&y, int &&z) { }
void baz(int &&x, int &&y, int &&z) { }

// claim ownership of the objects and create temporaries
void bam(int x, int y, int z) { }

int main() {

    int i = 1;
    foo(i,2,3);
    foo(2,i,3);
    foo(2,3,i);

    bar(i,2,3); // doesn't compile
    bar(2,i,3);
    bar(2,3,i);

    baz(i,2,3); // requires 8 overloads
    baz(2,i,3); 
    baz(2,3,i);

    return 0;
}   

我对所有解决方案都不完全满意,因为每个解决方案都有缺点。这个问题有没有更干净的替代方案?

这是完美的转发问题。避免 baz 的 8 个重载的方法是使用所谓的通用引用:

template <typename T, typename U, typename V>
void baz(T &&t, U &&u, V &&v) { }

在上面,tuv 忠实地复制了参数的 r/l-valueness。例如,假设您最初的预期功能是这样的:

void baz(vector<int> &t, vector<int> &u, vector<int> &v) { }

和其他 7 个重载。对于模板版本,如果您调用 baz(a,b,c),其中 ab 是右值,而 c 不是,TU 将被推断为 vector<int>&&V 将被推断为 vector<int>&。因此,您拥有此实现所需的所有信息。

这个问题实际上不是微不足道的,但有一些指导方针已经发展了多年,实际上并没有随着 C++11 发生太大变化。对于以下内容,我们将假设您有一个无副作用的独立函数(构造函数和某些成员函数的指南略有不同)。

我推荐的独立函数是:

  1. 按值传递原始数据类型(int、double、bool 等)
  2. 通过 const 引用传递复杂数据类型(例如 const std::vector&),除非您需要函数内的副本然后通过值传递
  3. 如果您的函数是模板化的,请使用 const 引用(例如 const T&),因为左值和右值都将绑定到 const 引用
  4. 如果你的函数是模板化的并且你打算完美转发那么使用转发引用(例如T&&,也称为通用引用)

因此,对于您使用整数的示例,我将使用 bam 函数:

void bam(int x, int y, int z) { }

Herb Sutter 在去年的 CppCon 上有一个有趣的演讲 其中包括函数的输入参数: https://www.youtube.com/watch?v=xnqTKD8uD64

Whosebug 上与此问题相关的另一个有趣的 post: Correct usage of rvalue references as parameters

构造函数更新:

构造函数与上述建议的主要区别在于更强调复制输入并使对象拥有 class。这同样适用于二传手。主要原因是更安全的资源管理(避免数据竞争或访问无效内存)。

template<class T>
struct lrvalue {
  T&t;
  lrvalue(T&in):t(in){}
  lrvalue(T&&in):t(in){}
};

lrvalue<int>。问题解决了?你可以打扮它们,也许继承自 reference_wrapper 或类似的东西而不是 T&t

你也可以在调用时进行投射:

template<class T>
T& lvalue(T&&t){return t;}
template<class T>
T& lvalue(T&t)=delete;//optional

现在,如果您想将 foo{} 传递给 foo&,您可以。 lvalue(foo{}).