将多个右值和左值传递给函数而不创建重载
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) { }
在上面,t
、u
和 v
忠实地复制了参数的 r/l-valueness。例如,假设您最初的预期功能是这样的:
void baz(vector<int> &t, vector<int> &u, vector<int> &v) { }
和其他 7 个重载。对于模板版本,如果您调用 baz(a,b,c)
,其中 a
和 b
是右值,而 c
不是,T
和 U
将被推断为 vector<int>&&
而 V
将被推断为 vector<int>&
。因此,您拥有此实现所需的所有信息。
这个问题实际上不是微不足道的,但有一些指导方针已经发展了多年,实际上并没有随着 C++11 发生太大变化。对于以下内容,我们将假设您有一个无副作用的独立函数(构造函数和某些成员函数的指南略有不同)。
我推荐的独立函数是:
- 按值传递原始数据类型(int、double、bool 等)
- 通过 const 引用传递复杂数据类型(例如 const std::vector&),除非您需要函数内的副本然后通过值传递
- 如果您的函数是模板化的,请使用 const 引用(例如 const T&),因为左值和右值都将绑定到 const 引用
- 如果你的函数是模板化的并且你打算完美转发那么使用转发引用(例如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{})
.
假设您正在为客户端库提供一个具有多个引用参数的函数。
为了简单起见,假设我们有 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) { }
在上面,t
、u
和 v
忠实地复制了参数的 r/l-valueness。例如,假设您最初的预期功能是这样的:
void baz(vector<int> &t, vector<int> &u, vector<int> &v) { }
和其他 7 个重载。对于模板版本,如果您调用 baz(a,b,c)
,其中 a
和 b
是右值,而 c
不是,T
和 U
将被推断为 vector<int>&&
而 V
将被推断为 vector<int>&
。因此,您拥有此实现所需的所有信息。
这个问题实际上不是微不足道的,但有一些指导方针已经发展了多年,实际上并没有随着 C++11 发生太大变化。对于以下内容,我们将假设您有一个无副作用的独立函数(构造函数和某些成员函数的指南略有不同)。
我推荐的独立函数是:
- 按值传递原始数据类型(int、double、bool 等)
- 通过 const 引用传递复杂数据类型(例如 const std::vector&),除非您需要函数内的副本然后通过值传递
- 如果您的函数是模板化的,请使用 const 引用(例如 const T&),因为左值和右值都将绑定到 const 引用
- 如果你的函数是模板化的并且你打算完美转发那么使用转发引用(例如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{})
.