Considera questo esempio di codice:
#include #include typedef std::function func1_t; typedef std::function func2_t; struct X { X (func1_t f) { } X (func2_t f) { } }; int main ( ) { X x([](){ std::cout << "Hello, world!\n"; }); }
Ero sicuro che non dovrebbe essere compilato, perché il compilatore non dovrebbe essere in grado di scegliere uno dei due costruttori. g ++ – 4.7.3 mostra questo comportamento atteso: dice che la chiamata del costruttore sovraccarico è ambigua. Tuttavia, g ++ – 4.8.2 lo compila con successo.
Questo codice è corretto in C ++ 11 o è un bug / funzionalità di questa versione di g ++?
In C ++ 11 …
Diamo un’occhiata alle specifiche del modello di costruzione di std::function
(che accetta qualsiasi Callable): [func.wrap.func.con] / 7-10
template
function(F f); template function(allocator_arg_t, const A& a, F f); 7 Richiede:
F
deve essereCopyConstructible
.f
saràCallable
(20.10.11.2) per i tipi di argomentiArgTypes
e return typeR
Il costruttore di copie e il distruttore diA
non generano eccezioni.8 Postcondizioni:!
!*this
se si verifica una delle seguenti condizioni:
f
è un puntatore a funzioneNULL
.f
è un puntatoreNULL
al membro.F
è un’istanza del modello di class di funzione e!f
9 Altrimenti,
*this
target una copia dif
inizializzata constd::move(f)
. [lasciato una nota qui]10 tiri: non genera eccezioni quando
f
è un puntatore di funzione o unreference_wrapper
per alcuniT
Altrimenti, può lanciarebad_alloc
o qualsiasi eccezione generata dalla copia diF
o dal costruttore di mosse.
Ora, costruendo o tentando di build (per la risoluzione di sovraccarico) una std::function
da a [](){}
(cioè con signature void(void)
) viola i requisiti di std::function
costruttore di std::function
.
[Res.on.required] / 1
Violazione delle precondizioni specificate nella funzione Richiede: il paragrafo risulta in un comportamento indefinito a meno che la funzione non usi: paragrafo specifica di generare un’eccezione quando viene violata la precondizione.
Quindi, AFAIK, anche il risultato della risoluzione di sovraccarico non è definito. Pertanto, entrambe le versioni di g ++ / libstdc ++ sono conformi in questo aspetto.
In C ++ 14, questo è stato modificato, vedere LWG 2132 . Ora, il modello di costruzione di conversione di std::function
è necessario per rifiutare SFINAE Callables incompatibili (maggiori informazioni su SFINAE nel prossimo capitolo):
template
function(F f); template function(allocator_arg_t, const A& a, F f); 7 Richiede:
F
deve essereCopyConstructible
.8 Note: Questi costruttori non devono partecipare alla risoluzione di sovraccarico a meno che
f
non sia Callable (20.9.11.2) per i tipi di argomentiArgTypes...
e restituisca il tipoR
[…]
Il “non partecipare alla risoluzione di sovraccarico” corrisponde al rifiuto tramite SFINAE. L’effetto netto è che se hai un sovraccarico di funzioni foo
,
void foo(std::function); void foo(std::function);
e un’espressione chiamata come
foo([](std::string){}) // (C)
quindi il secondo sovraccarico di foo
viene scelto in modo inequivocabile: poiché std::function
definisce F
come sua interfaccia verso l’esterno, la F
definisce quali tipi di argomenti sono passati a std::function
. Quindi, l’object funzione avvolto deve essere chiamato con tali argomenti (tipi di argomenti). Se un double
viene passato in std::function
, non può essere passato a una funzione che prende una std::string
, perché non c’è una conversione double
-> std::string
. Per il primo overload di foo
, l’argomento [](std::string){}
è quindi considerato Callable per std::function
. Il modello di costruzione è distriggersto, quindi non esiste una conversione valida da [](std::string){}
a std::function
. Questo primo sovraccarico viene rimosso dal set di sovraccarico per risolvere la chiamata (C), lasciando solo il secondo sovraccarico.
Si noti che c’è stata una leggera modifica alla dicitura precedente, a causa di LWG 2420 : C’è un’eccezione che se il tipo di ritorno R
di una std::function
è void
, allora viene accettato qualsiasi tipo di ritorno (e scartato) per il Callable nel modello di costruttore menzionato sopra. Ad esempio, sia []() -> void {}
e []() -> bool {}
sono Callable per std::function
. La seguente situazione produce quindi un’ambiguità:
void foo(std::function); void foo(std::function); foo([]() -> bool {}); // ambiguous
Le regole di risoluzione del sovraccarico non cercano di classificarle tra diverse conversioni definite dall’utente, e quindi entrambi gli overload di foo
sono validi (prima di tutto) e nessuno dei due è migliore.
Nota che quando un controllo SFINAE fallisce, il programma non è mal formato, ma la funzione non è valida per la risoluzione di sovraccarico. Per esempio:
#include #include template auto foo(T) -> typename std::enable_if< std::is_integral::value >::type { std::cout << "foo 1\n"; } template auto foo(T) -> typename std::enable_if< not std::is_integral::value >::type { std::cout << "foo 2\n"; } int main() { foo(42); foo(42.); }
Allo stesso modo, una conversione può essere resa non valida utilizzando SFINAE nel costruttore di conversione:
#include #include struct foo { template::value >::type > foo(T) { std::cout << "foo(T)\n"; } }; struct bar { template ::value >::type > bar(T) { std::cout << "bar(T)\n"; } }; struct kitty { kitty(foo) {} kitty(bar) {} }; int main() { kitty cat(42); kitty tac(42.); }
È totalmente valido Poiché le espressioni lambda di c ++ 11 (e il tuo std::function
wrapper) creano oggetti funzione. La grande forza degli oggetti funzione è che, anche quando sono generici, rimangono oggetti di prima class. A differenza dei normali modelli di funzione, possono essere passati e restituiti dalle funzioni.
È ansible creare set di overload operatore in modo esplicito con ereditarietà e utilizzando dichiarazioni. Il seguente uso di Mathias Gaunard dimostra “espressioni lambda sovraccaricate”.
template struct overload_set : F1, F2 { overload_set(F1 x1, F2 x2) : F1(x1), F2(x2) {} using F1::operator(); using F2::operator(); }; template overload_set overload(F1 x1, F2 x2) { return overload_set(x1,x2); } auto f = overload( [](){return 1;}, [](int x){return x+1;} ); int x = f(); int y = f(2);
fonte
EDIT: Forse diventerà più chiaro se nell’esempio fornito si sostituisce
F1 -> std::function F2 -> std::function
e vederlo compilare in gcc4.7
La soluzione basata su modelli è stata fornita solo per dimostrare che è ansible scalare il concetto di codice generico e dissociarsi.
Nel tuo caso, quando usi un compilatore più vecchio come gcc 4.7 , potresti aiutare con il cast esplicito e gcc risolverà le cose, come puoi vedere in questo esempio dal vivo
Nel caso ve lo stiate chiedendo, non funzionerebbe se girate il contrario (provate a convertire il lambda in take int alla funzione std :: senza prendere argomenti e così via)