右值函数重载

rvalue function overloading

本文关键字:重载 函数      更新时间:2023-10-16

我想重载一个函数,以便它以某种方式操作其参数,然后返回对该参数的引用 - 但如果参数不可变,那么它应该返回参数的操纵副本。在弄了很久之后,这就是我想出的。

using namespace std;
string& foo(string &in)
{
    in.insert(0, "hello ");
    return in;
}
string foo(string &&in)
{
    return move(foo(in));
}
string foo(const string& in)
{
    return foo(string(in));
}

这段代码似乎可以正常工作,但我有兴趣听听是否有人能想到更好的方法来做到这一点。

这是一个测试程序:

int main(void)
{
    string var = "world";
    const string var2 = "const world";
    cout << foo(var) << endl;
    cout << var << endl;
    cout << foo(var2) << endl;
    cout << var2 << endl;
    cout << foo(var + " and " + var2) << endl;
    return 0;
}

正确的输出是

hello world
hello world
hello const world
const world
hello hello world and const world

我想如果我能这样做会更整洁一些:

string& foo(string &in)
{
    in.insert(0, "hello ");
    return in;
}
string foo(string in)
{
    return move(foo(in));
}

当然,这是行不通的,因为大多数函数对foo的调用都是模棱两可的——包括foo本身的调用!但是,如果我能以某种方式告诉编译器优先考虑第一个......

正如我所说,我发布的代码工作正常。我不喜欢它的主要事情是重复的额外代码。如果我有一堆这样的功能,它会变得一团糟,而且大部分会非常重复。因此,作为我问题的第二部分:任何人都可以想到一种方法来自动生成第二和第三个foo函数的代码吗?例如

// implementation of magic_function_overload_generator
// ???
string& foo(string &in);
magic_function_overload_generator<foo>;
string& bar(string &in);
magic_function_overload_generator<bar>;
// etc

我会一起删除引用,只编写一个按值传递和返回的函数:

std::string foo(std::string in)
{
    in.insert(0, "hello ");
    return in;
}

如果传递左值,则将复制输入字符串。如果传递右值,它将被移动。

当离开函数时,命名的返回值优化可能会启动,因此返回基本上是无操作的。如果编译器决定不这样做,结果将被移动(即使in是一个左值(。

右值引用的好处是,您必须考虑在用户代码中放置引用的位置以提高效率。对于可移动类型,按值传递实际上与它一样高效。

整个问题是你为什么要有这样的重载?所有这些重载都指定了一个接口:foo(x(。但是 x parameter可能是inputinput/output参数,具体取决于其类型。它非常非常容易出错。用户应该做一些额外的工作来确保它的变量不会被改变。切勿在生产代码中执行此操作。

我同意这样的重载:

string foo(string &&in);
string foo(const string& in);

如果输入参数不是临时对象,则永远不会更改该参数,同时重复使用临时对象。这似乎很合理。

但是,为什么要生成大量这样的重载?&&&重载是为了优化。我会说非常微妙的优化。您确定在很多地方都需要它吗?

无论如何,如果你真的想生成C++代码,模板不是一个非常好的选择。我会为此使用一些外部工具。就个人而言,我更喜欢齿轮。

遵循简单的方法怎么样?

string& foo (string &change)  // this accepts mutable string
{
  change = string("hello ") + change;
  return change;
}
string foo (const string &unchange)  // this accepts not mutable string
{
  return string("hello ") + unchange;
}

在此处查看其输出。

与@iammilind的答案相同,但没有重复:

#include <iostream>
using namespace std;
string foo(const string &unchange) {
  return string("hello ") + unchange;
}
string& foo(string &change) {
  return change = foo(static_cast<const string&>(foo));
}
int main(int argc, char** argv) {
    string a = "world";
    const string b = "immutable world";
    cout << foo(a) << 'n' << foo(b) << 'n';
    cout << foo(a) << 'n' << foo(b) << 'n';
}

注意:您也可以在此处使用const_cast来添加const资格。

如果你不担心效率,你可以按值传递或按常量引用传递,然后做一个复制并完成它。

但是,如果您担心效率,我认为此回复中的传递值建议不是最好的方法。这是因为我认为它会导致额外的副本/移动,因为 NRVO 似乎只适用于局部变量,而不是参数。我认为避免在 C++0x 中移动/复制的方法是双重重载,如以下代码所示:

#include <iostream>
struct A
{
  A() : i(0) {}
  A(const A& x) : i(x.i) { std::cout << "Copy" << std::endl; }
  A(A&& x) : i(x.i) { std::cout << "Move" << std::endl; }
  void inc() { ++i; }
  int i;
};
A f1(const A& x2) { A x = x2; x.inc(); return x; }
A&& f1(A&& x) { x.inc(); return std::move(x); }
A f2(A x) { x.inc(); return std::move(x); }
int main()
{
  A x;
  std::cout << "A a1 = f1(x);" << std::endl;
  A a1 = f1(x);
  std::cout << "A a2 = f1(A());" << std::endl;
  A a2 = f1(A());
  std::cout << "A b1 = f2(x);" << std::endl;
  A b1 = f2(x);
  std::cout << "A b2 = f2(A());" << std::endl;
  A b2 = f2(A());
  std::cout << std::endl;
  std::cout << "A a3 = f1(f1(x));" << std::endl;
  A a3 = f1(f1(x));
  std::cout << "A a4 = f1(f1(A()));" << std::endl;
  A a4 = f1(f1(A()));
  std::cout << "A b3 = f2(f2(x));" << std::endl;
  A b3 = f2(f2(x));
  std::cout << "A b4 = f2(f2(A()));" << std::endl;
  A b4 = f2(f2(A()));
  std::cout << std::endl;
  std::cout << "A a5 = f1(f1(f1(x)));" << std::endl;
  A a5 = f1(f1(f1(x)));
  std::cout << "A a6 = f1(f1(f1(A())));" << std::endl;
  A a6 = f1(f1(f1(A())));
  std::cout << "A b5 = f2(f2(f2(x)));" << std::endl;
  A b5 = f2(f2(f2(x)));
  std::cout << "A b6 = f2(f2(f2(A())));" << std::endl;
  A b6 = f2(f2(f2(A())));
}

这将产生以下结果:

A a1 = f1(x);
Copy
A a2 = f1(A());
Move
A b1 = f2(x);
Copy
Move
A b2 = f2(A());
Move
A a3 = f1(f1(x));
Copy
Move
A a4 = f1(f1(A()));
Move
A b3 = f2(f2(x));
Copy
Move
Move
A b4 = f2(f2(A()));
Move
Move
A a5 = f1(f1(f1(x)));
Copy
Move
A a6 = f1(f1(f1(A())));
Move
A b5 = f2(f2(f2(x)));
Copy
Move
Move
Move
A b6 = f2(f2(f2(A())));
Move
Move
Move

您可以执行一些模板技巧来避免编写多个重载,例如:

template <class T>
param_return_type<T&&>::type f3(T&& y, typename std::enable_if<...>::type* dummy = 0 ) 
{ 
  typedef return_t param_return_type<T&&>::type;
  return_t x = static_cast<return_t>(y);
  x.inc();
  return static_cast<return_t>(x);
}

其中param_return_type<T>::type在传递(const) T&T,在传递T&&T&&std::enable_if<...>,如果您只希望此模板采用特定参数,则可以使用。

我不知道如何写param_return_type<T>::type的定义,因为似乎没有std::remove_lvalue_reference。如果有人知道如何,请随时编辑/添加到我的帖子中。