2015-04-15 69 views
25

我在玩C++中的lambdas重載技巧。具體做法是:C++中重載的lambdas以及clang和gcc之間的區別

// For std::function 
#include <functional> 

// For std::string 
#include <string> 

// For std::cout 
#include <iostream> 

template <class... F> 
struct overload : F... { 
    overload(F... f) : F(f)... {} 
};  

template <class... F> 
auto make_overload(F... f) { 
    return overload<F...>(f...); 
} 

int main() { 

    std::function <int(int,int)> f = [](int x,int y) { 
     return x+y; 
    }; 
    std::function <double(double,double)> g = [](double x,double y) { 
     return x+y; 
    }; 
    std::function <std::string(std::string,std::string)> h = [](std::string x,std::string y) { 
     return x+y; 
    }; 

    auto fgh = make_overload(f,g,h); 
    std::cout << fgh(1,2) << std::endl; 
    std::cout << fgh(1.5,2.5) << std::endl; 
    std::cout << fgh("bob","larry") << std::endl; 
} 

現在,上面的程序編譯和鐺正常工作:

$ clang++ -g -std=c++14 test01.cpp -o test01 
$ ./test01 
3 
4 
boblarry 

它不會在GCC編譯:

$ g++ -g -std=c++14 test01.cpp -o test01 
test01.cpp: In function 'int main()': 
test01.cpp:36:25: error: request for member 'operator()' is ambiguous 
    std::cout << fgh(1,2) << std::endl; 
         ^
In file included from test01.cpp:5:0: 
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note: candidates are: _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = std::basic_string<char>; _ArgTypes = {std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >}] 
    function<_Res(_ArgTypes...)>:: 
    ^
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note:     _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = double; _ArgTypes = {double, double}] 
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note:     _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = int; _ArgTypes = {int, int}] 
test01.cpp:37:29: error: request for member 'operator()' is ambiguous 
    std::cout << fgh(1.5,2.5) << std::endl; 
          ^
In file included from test01.cpp:5:0: 
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note: candidates are: _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = std::basic_string<char>; _ArgTypes = {std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >}] 
    function<_Res(_ArgTypes...)>:: 
    ^
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note:     _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = double; _ArgTypes = {double, double}] 
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note:     _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = int; _ArgTypes = {int, int}] 
test01.cpp:38:35: error: request for member 'operator()' is ambiguous 
    std::cout << fgh("bob","larry") << std::endl; 
           ^
In file included from test01.cpp:5:0: 
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note: candidates are: _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = std::basic_string<char>; _ArgTypes = {std::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::basic_string<char, std::char_traits<char>, std::allocator<char> >}] 
    function<_Res(_ArgTypes...)>:: 
    ^
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note:     _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = double; _ArgTypes = {double, double}] 
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2434:5: note:     _Res std::function<_Res(_ArgTypes ...)>::operator()(_ArgTypes ...) const [with _Res = int; _ArgTypes = {int, int}] 
Makefile:2: recipe for target 'all' failed 
make: *** [all] Error 1 

爲什麼是有區別嗎?爲了記錄,我使用gcc 4.9.2和clang 3.5.0。


編輯1

顯然,這個代碼片段無法編譯的VC以及和當時已經reported。話雖這麼說,肖恩Middleditch發佈的重載代碼的工作版本:

template<class F1, class... Fs> 
struct overload : F1, overload<Fs...> 
{ 
    using F1::operator(); 
    using overload<Fs...>::operator(); 
    overload(F1 f1, Fs... fs) : F1(f1), overload<Fs...>(fs...) {} 
}; 

template<class F1> 
struct overload<F1> : F1 
{ 
    using F1::operator(); 
    overload(F1 f1) : F1(f1) {} 
}; 


template <class... F> 
auto make_overload(F... f) { 
    return overload<F...>(f...); 
} 

我仍然有興趣瞭解爲什麼這個版本的重載拉姆達代碼的工作,但原來的一個沒有。

+1

這可能只是因爲你簡化了這個問題的代碼,但是因爲你使用的是C++ 14,所以''(auto x,auto y){return x + y;}'會產生一個lambda具有相同的重載功能。 –

+1

@DrewDormann當然。真的,我只是試圖拿出一個例子來展示正在發生的事情。後來,我想用它來處理更復雜的情況。 – wyer33

+0

令人驚訝的是在clang中編譯的原始代碼。一般規則是不同基類中的相同名稱不會超載。 –

回答

14

對我來說看起來像一個叮鐺聲。

一般規則是不同基類中的同名成員函數不會重載。例如:

struct Foo { void bar(); }; 
struct Baz { void bar(int); }; 
struct Quux : Foo, Baz { }; 

int main() { Quux().bar(); } // error on both GCC and Clang 

無論出於何種原因,未能鏘診斷這種模棱兩可的operator()

A using-declaration將指定的基類成員提升到派生類作用域,從而允許它們重載。因此:

struct Quux_2 : Foo, Baz { using Foo::bar; using Baz::bar; }; 
Quux_2().bar(); // OK. 

在代碼的工作版本,該using聲明遞歸帶來模板參數每operator()聲明成最派生類的範圍,使他們能夠過載。

+0

這是因爲名稱查找是在重載解析甚至開始之前完成的,並且名稱查找找到兩個都不是首選的「Foo :: bar」和「Baz :: bar」? –

9

原代碼不應該編譯,gcc在這裏是正確的。參見[class.member.lookup]:

否則(即,C不包含F的聲明或聲明所得集爲空),S(F,C)是 最初是空的。如果C具有基類,則計算每個直接基類子對象Bi, 中的f的查找集並依次將每個這樣的查找集合S(f,Bi)合併爲S(f,C)。
- [..]
- 否則,如果聲明集S的(F,BI)和S(F,C)不同,合併是曖昧...

初始聲明集合是空(overload沒有方法) - 所以合併所有的基地,他們都有不同的集合。所以合併應該失敗。該規則僅在overload的聲明集爲空時適用,這就是爲什麼明確添加using F1::operator()的原因。

相關問題