2011-06-16 87 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。如果有人知道如何,隨時編輯/添加到我的文章。