2012-10-19 18 views
4

在我自己的代碼中出現錯誤後,編譯器選擇了錯誤的超載,我一直在挖掘一個解釋,但找不到一個簡單的解釋。我確實找到Herb Sutter's GOTW 49這涉及到專業化問題。我還在stackoverflow上發現了一些問題,但沒有人能真正向我解釋原因,也沒有給我提供好的解決方案。由於轉換錯誤導致的超載/專業化不正確

我有一個類Foo,它可以從一個布爾值構造。我發現(困難的方式)std :: string也可以用bool(false)構造。

我有三個具有不同參數的(模板)方法,如下所示。一個方法接受「任何」模板參數,並接受兩個特化,接受一個結構Foo和另一個接受一個字符串。

#include <string> 
#include <iostream> 

struct Foo 
{ 
    Foo() : value(false){ }; 
    Foo(bool v) : value (v) { } 
    Foo(const bool& v) : value(v) { } 

    bool value; 
}; 

template< typename T > 
void bar(const T& value) 
{ 
    std::cerr << "template bar" << std::endl; 
} 

template< > 
void bar<Foo>(const Foo&) 
{ 
    std::cerr << "template bar with Foo" << std::endl; 
} 

template< typename T > 
void bar(const std::string&) 
{ 
    std::cerr << "template bar with string" << std::endl; 
} 

int main(int argc, char* argv[]) 
{ 
    bar(false); // Succeeds and calls 1st bar(const T&) 
    bar<Foo>(false); // Crashes, because 2nd bar(const std::string&) 
         // is called with false promoted to null pointer. 

    return 0; 
} 

我已經用Visual Studio 2010和MinGW(gcc 4.7.0)測試過了。 GCC很好地給出了一個編譯警告,但MSVC不會:

main.cpp:34:20: warning: converting 'false' to pointer type for argument 1 of 'std::basic_string< ... ' [-Wconversion-null] 

小更新(代碼):即使富明確的專業化不起作用\

小更新2:編譯器不抱怨「模糊超載」。

小小更新3:有些人回答說,Foo的兩個構造函數接受bool,「無效」Foo的選擇。我只用一個轉換構造函數測試過類似的版本。這些也不起作用。

問題:

  1. 爲什麼編譯器嘗試調用字符串參數版本?
  2. 爲什麼在bar()調用中添加<Foo>很重要。
  3. 我該如何防止這種情況發生。例如。當輸入bool時,我可以強制編譯器選擇bar(const Foo&)嗎?
  4. 或者,我可以在有人撥打bar<Foo>(false)時執行編譯錯誤嗎?

回答

4

這裏有答案了四個問題:

  1. 爲什麼編譯器嘗試調用字符串參數版本?您的類Foo有兩個構造函數,其中bool含糊不清,因此不會考慮將bool轉換爲Foo(如果要檢測是否傳入臨時值或左值,可以使用bool&&bool const&作爲參數在C++中重載類型)。 std::string可以從char const*false構建,可以被提升爲空指針常量。一個空指針常量在語法上是一個有效的char const*,但它是一個非法值,並通過它導致未定義的行爲。
  2. 爲什麼Bar()中的additon函數很重要?當指定模板參數時,您可以取消模板參數推導,並告知編譯器使用哪個參數。明確指定模板參數是bar()採用std::string的超負荷的唯一方法,因爲無法爲此模板推導T
  3. 我該如何防止這種情況發生。例如。我可以強制編譯器在輸入bool時選擇bar(const Foo &)嗎?最簡單的方法是讓編譯器推導出模板參數:由於編譯器無法推導出模板參數,因此編譯器不會自動選擇bar()的版本。或者,如果要明確指定模板參數,並且只有bool是個問題,則可以添加一個過載,採用bool,並將推導的版本和bool版本委託給相同的內部函數。
  4. 另外,我可以強制執行一個編譯錯誤,當有人叫酒吧< Foo>(false)?這很容易使用C++ 2011刪除重載(參見下文)。

要使用bool有明確指定模板參數時,創建一個編譯時錯誤,您可以添加此重載:

template <typename T> void bar(bool) = delete; 

刪除功能是在C++ 2011年

主要提供問題似乎如下:如果Foostd::string都可以從bool轉換,爲什麼std::string轉換選擇如果bar<Foo>(bool)被稱爲和以下重載可用?

template <typename T> void bar(T const&); 
template <>   void bar<Foo>(Foo const&); 
template <typename T> void bar(std::string const&); 

首先,重載決議選擇主模板,忽略任何專業化)。由於bar(std::string const&)作爲比推導模板參數的版本更專用的界面,所以選擇該版本。第一個模板的專業化在這個階段被忽略。爲了使適用的使用Foo還有,你可以添加

template <typename T> void bar(Foo const&); 

,並呼籲bar<Foo>(false)將是std::stringFoo版本之間的歧義。

+0

所有四個回答的第一個。謝謝。關於答案1:Foo也可以由false構建......爲什麼它會選擇bar(string)而不是bar(Foo)? –

+0

你在對一個自從被刪除的答案的評論中說,你只從'const bool&'構造函數開始。事情是,我不認爲字面上的'false'是'const bool&'。你可以嘗試評論構造函數出來,看看它是否工作? –

+0

已經嘗試了所有三種組合:兩種構造函數,一種使用bool,另一種使用const bool&。不起作用。 –

1

此行

bar<Foo>(false); 

可以匹配這兩個函數:

void bar<Foo>(const Foo&); 
void bar<Foo>(const std::string&); 

現在,如果雙方的UDT具有bool隱式轉換構造函數,其中之一應該是選擇?它們都具有相同的優先級,因爲它們都是相同類型的轉換。

至少......據推測這就是發生了什麼,雖然它真的不應該選擇一個促進+建設在一個建築只序列。

+0

如果它匹配兩種方法,爲什麼編譯器不會抱怨「模糊重載」(也作爲問題添加)? –

+0

Dietmar的答案解釋說,具有_two_隱式構造函數可以匹配相同的優先級,從而不考慮「Foo」構造路徑。你每天都會學到一些東西! – Useless

2

第一個示例bar(false);調用template<typename T> void bar(const T& value),因爲它與T = bool完全匹配。

當您指定T = Foo時,這兩個重載都不再是精確匹配,因此您可以進入關於應用哪些隱式轉換的相當複雜的規則。大多數C++程序員都不能完全理解這些,所以你最好避免隱式轉換。

在這種情況下最簡單的修復方法是爲bool添加另一個重載。

template<typename T> 
void bar(bool) 
{ 
    std::cerr << "bool" << std::endl; 
} 

然後在那個過載中你可以明確地應用轉換並調用你想要的版本。