将多个右值和左值传递给函数而不创建重载
Posted
技术标签:
【中文标题】将多个右值和左值传递给函数而不创建重载【英文标题】:passing multiple rvalues and lvalues to a function without creating overloads 【发布时间】:2015-06-03 23:40:45 【问题描述】:假设您正在为客户端库提供具有多个引用参数的函数。
为了简单起见,假设我们有 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;
我对所有解决方案并不完全满意,因为它们中的每一个都有缺点。有没有更清洁的方法来解决这个问题?
【问题讨论】:
我只会默认使用 C++03const&
,除非分析器显示出为给定用例添加 rref 重载的一些巨大优势。
这是完美的转发问题。规范的解决方案是直接(通用引用和 srtd::forward)或间接(按值传递)使用右值引用。你说的缺点是什么?当然,用int
来说明是个坏主意。 (注意:您的模板包装器解决方案不正确,因为您缺少 std::forward。)
@Pradhan 我特别不想在示例中使用map<string, vector<boost::variant<X,Y,Z>>
。缺点在于 cmets。到目前为止,我有一个标准的可变参数模板函数,我在这种情况下粘贴了它,它会慢慢地乱扔我的源代码。复制大的东西不是我想要的方式,也会造成很多重载。
@Alex 您能否为传递值和“通用参考”模板解决方案添加 cmets?此信息将有助于回答您的具体问题。
@Alex 你没有提到最明显的解决方案void qux(int const &x, int const &y, int const &z)
。所以我猜你有一些想法,你可能希望参数是可变的,但这与bar(i,2,3)
不相符,因为2
是不可变的。因此,您需要明确描述该函数的输入条件和行为。
【参考方案1】:
这个问题实际上并不是微不足道的,但有一些指导方针多年来一直在发展,实际上在 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
关于***的另一个有趣的帖子与这个问题有关: Correct usage of rvalue references as parameters
构造函数更新:
构造函数与上述建议的主要区别在于更强调复制输入并使对象归类所有。对于二传手也是如此。主要原因是更安全的资源管理(避免数据竞争或访问无效内存)。
【讨论】:
如果您不介意从构造函数中添加角盒,因为我认为这是必不可少的。【参考方案2】:这是完美的转发问题。避免baz
的8 个重载的方法是使用所谓的通用引用:
template <typename T, typename U, typename V>
void baz(T &&t, U &&u, V &&v)
在上面,t
、u
和 v
忠实地复制了参数的 r/l 值。例如,假设您最初的预期功能是这样的:
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>&
。因此,您拥有此实现所需的所有信息。
【讨论】:
@Alex 抱歉,无法弄清楚 cmets 缺少什么。您能否对此答案发表评论并帮助我理解您的问题?【参考方案3】: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)
.
【讨论】:
以上是关于将多个右值和左值传递给函数而不创建重载的主要内容,如果未能解决你的问题,请参考以下文章