2015-05-22 53 views
29

我想了解爲什麼std::function無法區分重載函數。std ::函數無法區分重載函數

#include <functional> 

void add(int,int){} 

class A {}; 

void add (A, A){} 

int main(){ 
     std::function <void(int, int)> func = add; 
} 

在上面所示的代碼,function<void(int, int)>只能匹配的這些功能中的一個,但它失敗。這是爲什麼?我知道我可以通過使用實際函數的lambda或函數指針來解決此問題,然後將函數指針存儲在函數中。但爲什麼這會失敗?上下文不清楚我想選擇哪個功能?請幫我理解爲什麼這會失敗,因爲我無法理解爲什麼模板匹配在這種情況下失敗。是

,我得到鐺這個編譯器錯誤如下:

test.cpp:10:33: error: no viable conversion from '<overloaded function type>' to 
     'std::function<void (int, int)>' 
     std::function <void(int, int)> func = add; 
            ^ ~~~ 
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1266:31: note: 
     candidate constructor not viable: no overload of 'add' matching 
     'std::__1::nullptr_t' for 1st argument 
    _LIBCPP_INLINE_VISIBILITY function(nullptr_t) : __f_(0) {} 
          ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1267:5: note: 
     candidate constructor not viable: no overload of 'add' matching 'const 
     std::__1::function<void (int, int)> &' for 1st argument 
    function(const function&); 
    ^
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/__functional_03:1269:7: note: 
     candidate template ignored: couldn't infer template argument '_Fp' 
     function(_Fp, 
    ^
1 error generated. 

編輯 - 除了MSalters的回答,我沒有在這個論壇上一些搜索和發現的確切原因失敗。我在post得到了納瓦茲回覆的答案。

int test(const std::string&) { 
     return 0; 
    } 

    int test(const std::string*) { 
     return 0; 
    } 

    typedef int (*funtype)(const std::string&); 

    funtype fun = test; //no cast required now! 
    std::function<int(const std::string&)> func = fun; //no cast! 

那麼,爲什麼std::function<int(const std::string&)>不起作用funtype fun = test以上的工作方式:

我已經複製從他的回答粘貼在這裏?

那麼答案是,因爲std::function可以用任何物體,如其構造是模板化是獨立的,你傳遞給std::function模板實參的初始化。

+3

哪個編譯失敗呢? – EdChum

+0

gcc 4.8,clang和Visual Studio 2013。如果需要,我可以發佈編譯器錯誤,儘管它們不是非常友好。 – Madhusudhan

+0

編譯錯誤通常不友好但值得發佈它,這很奇怪,這在這裏變得模糊不清,就好像A類的類型在某種程度上被看作與'int'一樣,如果你聲明瞭錯誤仍然會發生'加'作爲'雙'? – EdChum

回答

21

很明顯我們在哪裏工作,你打算選擇,但是編譯器必須遵循C的規則++不使用邏輯的聰明飛躍(甚至不是那麼聰明的,就像這樣的簡單情況!)

std::function相關的構造函數是:

template<class F> function(F f); 

這是接受任何類型的模板。

的C++ 14標準確實限制了模板(因爲LWG DR 2132),使得它:

不得參加重載除非f爲參數類型ArgTypes...和返回類型可贖回(20.9.12.2) R

這意味着,編譯器將只允許調用構造函數時Functorstd::function的調用簽名(這是在你的例子void(int, int))兼容。理論上這應該表示void add(A, A)不是一個可行的論點,所以「明顯」你打算使用void add(int, int)

然而,編譯器無法檢驗「f是可調用的參數類型......」約束,直到它知道的f的類型,這意味着它需要有之前void add(int, int)void add(A, A)之間已經消除歧義它可以應用允許它拒絕其中一個功能的約束!

所以這是一個雞和蛋的問題,不幸的是意味着你需要通過精確地指定要使用哪一種add超載能力來幫助編譯出來,然後編譯器可以應用約束,(有點多餘)決定它是構造函數可接受的參數。

可以想象的是,我們可以改變C++,以便在這樣的情況下所有重載函數對約束測試(所以我們並不需要知道測試之前測試哪一個),如果只有一個是可行的然後使用那個,但那不是C++的工作原理。

+0

非常感謝Jonathan Wakely。在發佈這個答案之前,我已經編輯了這個問題,以表明我從另一個相關問題中瞭解了模板化構造函數。但你的答案是最接近我想要的。非常感謝你的澄清。 – Madhusudhan

+1

當參數是一個重載集時,標準確實需要試用推導,但只有當參數是指向函數指針,成員函數指針或函數類型(請參閱[temp.deduct.call]/p6)級別的cv限定詞和參考。 –

+0

@ T.C。這是一個非常有趣的想法,但是在選擇過載時只允許扣除失敗,而不是替代失敗。所以沒有辦法在'std :: function'這樣的通用上下文中應用這個機制。 – Potatoswatter

4

嘗試:

std::function <void(int, int)> func = static_cast<void(*)(int, int)> (add); 

地址到void add(A, A)void add(int, int) obvoiusly differes。當你用名字指向函數時,編譯器知道你需要哪個函數地址是不可能的。 void(int, int)這裏不是提示。

+1

嗨,我知道如何解決這個問題。但我的問題是「爲什麼首先有一個模棱兩可的問題」?功能參數是不是應該有助於選擇正確功能的簽名的一部分? – Madhusudhan

16

雖然很明顯,你想要什麼,問題是,std::function不能影響的&add重載解析。如果你要初始化一個原始函數指針(void (*func)(int,int) = &add),它確實有效。這是因爲函數指針初始化是重載解析完成的上下文。目標類型是完全已知的。但std::function將採取幾乎任何可調用的參數。這種接受參數的靈活性意味着你不能在&add上做重載分辨率。多重過載add可能是合適的。

明確的投射將起作用,即static_cast<void(*)(int, int)> (&add)

這可以在template<typename F> std::function<F> make_function(F*)包裹這將允許你寫auto func = make_function<int(int,int)> (&add)

+0

謝謝MSalters。這是否會在未來發生變化?重載解析似乎是一個有效的事情,因爲在創建'函數'對象時,我確切地指定了我想要的可調用對象的類型。我明白這個函數可以處理帶有重載操作符()或lambdas或常規函數的類。但不應該重載解決方案(儘管嚴格來說,這裏沒有重載解決方案,因爲lambdas和類本質上不會重載常規函數)或類似的東西在這裏適用 – Madhusudhan

+2

@ user3493289:我不希望它將來會改變。請注意,如果'int'可隱式轉換爲'A'並返回,則'std :: function '也可以包含'A add(A,A)'。正是這種靈活性使得超載不僅在技術上,而且在邏輯上也是不可能的。 – MSalters

+1

呵呵。我寫了這麼多的函數oid類,我顯然忘了'std :: function '缺少'void(*)(int,int)'重載。這樣的超負荷是不是無害的,並導致上述工作? – Yakk

-2

據我所知,這是一個Visual Studio問題。

C++ 11標準(11年8月20日)

std::function synopsis 
template<class R, class... ArgTypes> class function<R(ArgTypes...)>; 

但VisualStudio的不具有專業化

鐺++和g ++與重載的std ::功能

之前完全沒有答案解釋爲什麼VS不工作,但他們沒有提到它的VS」錯誤

+0

以前的答案給出瞭解釋說明你的答案是不正確的。你有沒有一個例子說明你的回答如何給已發佈的答案增加一些內容? – Prune

+0

事先答案解釋不好的VS行爲,就是這樣。他們誤導了。這很簡單,請閱讀標準 –

1

另一種方式來處理,這是在C++ 14的通用拉姆達:

int main() { 
    std::function <void(int, int)> func = [](auto &&... args) { add(std::forward<decltype(args)>(args)...); 
} 

這將創建一個lambda函數,可以毫不含糊地解決問題。 我沒有轉發參數,