2015-04-15 25 views
25

Sto giocando con un trucco per sovraccaricare lambda in C++. In particolare:lambda sovraccaricato in C++ e differenze tra clang e 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; 
} 

Ora, il programma di cui sopra compila e funziona bene in clang:

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

Esso non può essere compilato in 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 

Perché c'è una differenza? Per la cronaca, sto usando gcc 4.9.2 e clang 3.5.0.


Modifica 1

Evidentemente, questo frammento di codice non è riuscito a compilare su VC così ed era già stata reported. Detto questo, Sean Middleditch ha registrato una versione funzionante del codice sovraccarico:

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...); 
} 

Sono ancora interessato a capire il motivo per cui questa versione del codice Lambda sovraccarico funziona, ma l'originale non lo fa.

+1

Questo potrebbe essere dovuto alla semplificazione del codice per questa domanda, ma dal momento che stai usando C++ 14, '[] (auto x, auto y) {return x + y;}' produrrebbe un lambda con le stesse capacità sovraccariche. –

+1

@DrewDormann Di sicuro. Davvero, stavo solo cercando di trovare un singolo esempio per mostrare cosa stava succedendo. Più tardi, mi piacerebbe usarlo per casi più complicati. – wyer33

+0

È sorprendente che il codice originale sia stato compilato in clang. La regola generale è che gli stessi nomi in diverse classi base non sovraccaricano. –

risposta

14

Sembra un bug Clang per me.

La regola generale è che le funzioni membro con lo stesso nome in diverse classi base non sovraccaricano. Per esempio:

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

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

Per qualsiasi motivo, Clang non riesce a diagnosticare questa ambiguità per operator().

A using-declaration solleva i membri della classe di base denominata nell'ambito della classe derivata, consentendo loro di sovraccaricare. Quindi:

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

Nella versione funzionante del codice, le dichiarazioni using ricorsivamente portare ogni operator() dichiarazione negli argomenti di modello nel campo di applicazione della classe più derivata, permettendo loro di sovraccarico.

+0

Questo perché la ricerca del nome viene eseguita prima che inizi la risoluzione del sovraccarico, e la ricerca del nome trova sia 'Foo :: bar' che' Baz :: bar' di cui nessuno dei due è preferito? –

9

Il codice originale non deve compilare, gcc è corretto qui. Vedi [class.member.lookup]:

Altrimenti (cioè, C non contiene una dichiarazione di f o set dichiarazione risultante è vuoto), S (f, C) è inizialmente vuoto. Se C ha classi base, calcola il set di ricerca per f in ciascuna classe base diretta subobject Bi, e unisci ciascun set di ricerca S (f, Bi) a sua volta in S (f, C).
- [..]
- Altrimenti, se gli insiemi dichiarazione di S (f, Bi) e S (f, c) differiscono, l'unione è ambigua ...

Il set dichiarazione iniziale è vuoto (overload non ha metodi) - quindi unisci tutte le basi, tutte con set diversi. Quindi l'unione non dovrebbe riuscire. Questa regola si applica solo se la serie di dichiarazioni di overload è vuota, ed è per questo che l'aggiunta esplicita di using F1::operator() funziona.

Problemi correlati