2011-06-16 51 views
5

我想重载一个函数,以便它以某种方式操作它的参数,然后返回参数的引用 - 但是如果参数不可变,那么它应该返回一个操纵的副本的参数代替。 经过一段时间的搞乱之后,这里就是我想到的。rvalue函数重载

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的函数调用都是不明确的 - 包括01中的调用本身!但如果我能以某种方式告诉编译器优先考虑第一个...

正如我所说的,我发布的代码工作正常。我不喜欢它的主要原因是重复的额外代码。如果我有一堆这样的功能,它会变得相当混乱,而且大部分都是非常重复的。作为我的问题的第二部分:任何人都可以想出一种方法来自动生成第二个和第三个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 
+7

这听起来很可怕。根据我传递给函数的类型,返回值*和参数*的结果状态可能完全不同。这只是要求微妙的错误。为什么不让用户决定是否要通过显式调用不同的函数来修改对象或返回副本? – jalf 2011-06-16 08:05:09

+0

对我来说这似乎并不特别可怕,但也许你是对的。我想到的方式是该功能在输入时会改变输入;但如果它不能......那么它不会,但它仍然会给出正确的返回值。 我可能会使用它的一种东西就像是一个“标点符号”函数,它使用了一个不好打断的字符串并对其进行修复。您可能希望将结果直接发送到cout,或者您可能想在之后对字符串执行一些其他操作。所以有时你可能会传递不变的价值观,有时候......你明白了。 – karadoc 2011-06-16 08:26:07

+1

但我的观点是,无论是其中一个还是另一个发生都不取决于程序员想要什么,而是取决于某些相对微妙的语义细节(参数类型是否为const?是否是右值?),如果程序员没有明确地做出“现在我想返回一个副本,而不是修改对象”的决定,它可能很容易随时间而改变。我明白你想要做什么,但这是程序员可以轻易做出的决定,以及在哪里让你的图书馆做出错误的猜测可能会有非常糟糕的后果。 – jalf 2011-06-16 08:32:35

回答

4

我会摆脱引用的所有一起,只是写一个函数,传递并返回值:

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

如果传递的左值,输入的字符串会被复制。如果你传递一个右值,它将被移动。

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

关于右值引用的好处是您必须认为少于有关将引用放在用户代码中以提高效率的位置。对于可移动类型,传递值实际上与其获得的效率一样。

+0

这基本上是我最终做的。贾尔夫和其他人,让我相信我正在过度设计整个事情。一个函数不需要同时执行可变和不可变版本 - 如果一个函数确实执行了这两个操作,事实上可能会更加混乱。我想我使用右值引用来做一些很酷的事情让我兴奋不已。我有种希望,我从来没有听说过右值引用,那么我会首先选择这个解决方案,而不是浪费半天的时间试图做一些奇特的事! – karadoc 2011-06-17 00:43:28

+0

不要担心,酷的东西仍然发生 - 在引擎盖下;) – fredoverflow 2011-06-17 05:50:59

1

以下简单方法怎么办?

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; 
} 

看到它的output here

+0

该方法的坏处是函数的主体必须被写入两次。在这个特定的例子中,它比我所拥有的更好,但是如果foo是一个长而复杂的东西,那么这会使所需的代码增加一倍。 – karadoc 2011-06-16 08:22:37

+2

@karadoc:你可以很容易地写出非变异的变异。 'int notModifying(const int&i){int ii = i;退货修改(ii); }' – 2011-06-16 08:53:10

0

在同样作为@ iammilind的答案,但SANS重复:

#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资格。

2

整个问题是为什么你想要这样的重载?所有这些重载指定一个接口:foo(x)。但根据其类型,x parameter可能是inputinput/output参数。这是非常非常容易出错的。用户应该做一些额外的工作,以确保其变量不会发生变异。不要在生产代码中这样做。

我会用这样的重载同意:如果它不是一个临时

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

输入参数是从来没有改变过,并在同一时间,你使用这个临时对象。这似乎很合理。

但是,为什么你要产生很多这样的重载? & &过载是为了优化。我会说非常微妙的优化。你确定你需要在很多地方吗?

无论如何,如果你真的想生成C++代码,模板不是一个很好的选择。我会为它使用一些外部工具。我个人更喜欢Cog

0

如果你不担心效率,你可以通过值传递或通过const引用传递,做一个副本并完成它。

但是,如果您担心效率问题,我认为reply这种传递价值的建议不是最好的方法。这是因为我认为它会导致额外的副本/移动,因为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>::typeT通过(const) T&时,当通过T&&T&&std::enable_if<...>如果您只希望此模板采用特定参数,您可以使用它。

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