2012-10-31 47 views
7

我最近在嘗試使用完美的轉發構造函數實現類層次結構時遇到了問題。 請看下面的例子:在類層次結構中完美的轉發構造函數和複製構造函數之間的衝突

struct TestBase { 
    template<typename T> 
    explicit TestBase(T&& t) : s(std::forward<T>(t)) {} // Compiler refers to this line in the error message 

    TestBase(const TestBase& other) : s(other.s) {} 

    std::string s; 
}; 

struct Test : public TestBase { 
    template<typename T> 
    explicit Test(T&& t) : TestBase(std::forward<T>(t)) {} 

    Test(const Test& other) : TestBase(other) {} 
}; 

當我嘗試編譯代碼我收到以下錯誤:

Error 3 error C2664: 'std::basic_string<_Elem,_Traits,_Alloc>::basic_string(const std::basic_string<_Elem,_Traits,_Alloc> &)' : cannot convert parameter 1 from 'const Test' to 'const std::basic_string<_Elem,_Traits,_Alloc> &'

我的理解是,編譯器將完美轉發構造函數比一個更好的數學複製構造函數。例如參見Scott Meyers: Copying Constructors in C++11。在沒有類層次結構的其他實現中,我可以通過SFINAE禁用完美的轉發構造函數作爲複製構造函數。例如參見Martinho Fernandes: Some pitfalls with forwarding constructors。當我嘗試將上述解決方案應用於此示例時,我仍然無法使用相同的錯誤消息進行編譯。

我認爲一個可能的解決方案是避免完美的轉發,按構造函數中的值取參數,並從它們移動到類變量。

所以我的問題是,如果有這個問題的其他解決方案,或者如果在這種情況下不可能完美轉發?

更新: 原來,我的問題是容易產生誤解。所以我會試着澄清我的意圖和背景。

  • 該代碼是完全像發佈在問題中。沒有創建其他對象或調用函數。嘗試編譯發佈的示例時出現錯誤。
  • 擁有完美的轉發構造函數的目的是爲了成員初始化,並且而不是有一些額外的拷貝構造函數。原因在於在初始化具有臨時對象的成員時保存一些對象副本(如Scott Meyers的談話中提出的那樣)
  • 不幸的是,事實證明,完美的轉發構造函數可能與其他重載的構造函數衝突(在本例中使用複製構造函數) 。
  • 一樣的答案和評論對這個問題建議:可能的解決方案在這裏將引入顯式轉換或具有獨立的非模板的構造函數(即關於分別具有兩個構造帶參數const string&string&&的例子)。
+0

也許這是你寫的東西像'的std :: string(測試)',而不是'的std :: string(test.s) '?請向我們展示main.cc – Andrey

+2

的第130行,你寫了's(std :: forward (t))'而不是s(std :: forward (t).s)''。 – avakar

+1

混合過載和轉發模板通常是一個糟糕的主意。我從'string'創建一個構造函數並添加一個轉發'make_test' *函數*模板。 –

回答

2

嘗試改變Test(const Test& other) : TestBase(other) {}Test(const Test& other) : TestBase(static_cast<TestBase const&>(other)) {}

第二屆Test構造呼籲TestBase,有兩種可能性。其中一個需要任何東西,另一個需要一個TestBase。但是你正在通過一個測試 - 「任何事物」比較好。通過明確地轉換成TestBase const &,我們應該能夠找到合適的人來匹配。

另一種可能性可能涉及如何構建測試 - 也許你通過什麼將模板構造函數匹配到Test來代替?我們可以通過從Test中移除模板構造函數並查看錯誤是否消失來測試這種其他可能性。

如果是這種情況,爲什麼您鏈接的技術(在類型推斷匹配Test時禁用Test模板構造函數)工作?

+0

您的解決方案編譯沒有錯誤。在應用您的解決方案時是否會有任何可能的副作用?通常我會在沉默與錯誤/警告與演員時有點小心。 – mkh

+0

有問題的演員是安全的。我把一個Foo const&放入了一個FooBase const& - 你想隱式地做的事情,但是編譯器更喜歡你的模板構造函數來接受所有的東西。我明確表示你想隱含發生什麼... – Yakk

+0

感謝您的解釋。還有一個問題:如果我現在向Test添加一個移動構造函數,如下所示:'Test(Test && other):TestBase(std :: move(other)){}'。我再次收到相同的錯誤消息。你可能也有這種情況的解決方案?坦克很棒! – mkh

1

讓我們仔細看看錯誤消息。

std::basic_string<...>::basic_string(const std::basic_string<...> &) :

這意味着它適用於std::string

cannot convert parameter 1 from 'const Test' to 'const std::basic_string<..> &

事實上拷貝構造函數,有沒有辦法轉換從Teststd::string。但是,Test有一個字符串成員,即std::string s;

結論:看起來您忘記在該位置添加.s。可能是s(std::forward<T>(t))

另一個可能的原因是,構造函數的第一個重載被選中而不是第二個用於拷貝構造Test的實例。

+0

我不認爲我忘了'.s'。我的想法是參數t本身就是我想要轉發給成員的一種字符串。例如一個const char *或一個std :: string。 – mkh

+0

@mkh:那麼這意味着它發生了某種事情,T不是一個字符串,而是'Test'。 – Andrey

+0

比我的問題是我該如何讓編譯器停止思考T可能是一個測試。我不創建任何對象或調用任何函數。 – mkh

1

以下應工作,它不使用顯式轉換:

struct Test : public TestBase { 
    private: 
    static TestBase const& toBase(const Test& o) { return o; } 

    public: 
    template <typename T> 
    explicit Test(T&& t) : TestBase(std::forward<T>(t)) {} 

    Test(const Test& other) : TestBase(toBase(other)) {} 
};