Risoluzione di sovraccarico con std :: function

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 essere CopyConstructible . f sarà Callable (20.10.11.2) per i tipi di argomenti ArgTypes e return type R Il costruttore di copie e il distruttore di A non generano eccezioni.

8 Postcondizioni:! !*this se si verifica una delle seguenti condizioni:

  • f è un puntatore a funzione NULL .
  • f è un puntatore NULL al membro.
  • F è un’istanza del modello di class di funzione e !f

9 Altrimenti, *this target una copia di f inizializzata con std::move(f) . [lasciato una nota qui]

10 tiri: non genera eccezioni quando f è un puntatore di funzione o un reference_wrapper per alcuni T Altrimenti, può lanciare bad_alloc o qualsiasi eccezione generata dalla copia di F 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 essere CopyConstructible .

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 argomenti ArgTypes... e restituisca il tipo R

[…]

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.


Come può la SFINAE aiutare qui?

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)