2014-10-10 11 views
9

Se ho questo semplice caso:Perché std :: bind account per la funzione arity?

struct Foo 
{ 
    void bar(); 
    void baz(int); 
}; 

Ha senso che questa sarebbe la compilazione:

Foo foo; 
auto f = std::bind(&Foo::bar, &foo); 

Ma perché sarebbe bind essere progettato in modo tale che questo compila:

auto g = std::bind(&Foo::baz, &foo); 

Posso chiamare f, ma non posso mai chiamare g. Perché perfino farlo compilare? Qual è la logica alla base che richiede me avere a che fare:

auto g2 = std::bind(&Foo::baz, &foo, std::placeholders::_1); 

posso capire utilizzando i segnaposto se si vuole pasticciare con che argomenti vengono passati e in quale ordine, ma perché non solo avere il pass di default all gli argomenti nel giusto ordine senza doverlo specificare?

+1

Probabilmente perché non v'è alcun modo per ottenere il numero di argomenti di una funzione altrimenti, il che significa che l'oggetto funzione generato dovrebbe consentire * qualsiasi * numero di argomenti, incluso nessuno, che porterebbe a un comportamento indefinito se (ad esempio) 'g' è stato" chiamato "senza argomenti. –

+6

Penso che 'std :: placeholders' esegua due funzioni, 1. come dici tu, indirizzano gli argomenti da source a sink, 2. indicano quanti argomenti sono richiesti. – Niall

+3

Le librerie di boost possono avere più delle motivazioni di progettazione originali relative a 'bind'. – Niall

risposta

3

Ma perché avrebbe vincolato essere progettati in modo tale che questo compila:
auto g = std::bind(&Foo::baz, &foo);
posso chiamare f, ma non posso mai chiamare g. Perché perfino farlo compilare?

Il Boost.Bind FAQ dice che Boost.Bind di solito diagnosticare tali errori in "tempo legare" (vale a dire sulla linea in cui si chiama bind). Tuttavia la norma non richiede che per std::bind, invece di avere la seguente nella Richiede elemento per std::bind:

INVOKE (fd, w1, w2, ..., wN) (20.9.2) deve essere un'espressione valida per alcuni valori w1, w2, ..., wN, dove N == sizeof...(bound_args).

Ciò significa che il codice viola la precondizione della funzione, che si traduce in un comportamento non definito. L'implementazione della libreria standard non è obbligata a verificare le violazioni di precondizione, questo è il tuo lavoro. La libreria non ha il divieto di controllarli, quindi sarebbe conforme per un'implementazione rifiutarla, come fa Boost.Bind. Vorrei fare una richiesta al tuo rivenditore di librerie chiedendo loro di diagnosticare espressioni di binding non valide dove è possibile farlo, come un miglioramento della "Qualità di implementazione".

perché non solo hanno il pass di default tutti gli argomenti nel giusto ordine, senza dover specificare esso?

Posso pensare a due ragioni.

In primo luogo, il comportamento della chiamata involucro creato da bind è far cadere gli argomenti che non corrispondono ad un segnaposto, in modo da poter chiamare x(1, 2, 3) e lo hanno ignorare tutti gli argomenti e chiamare foo.bar(). Questo fa parte di un modello generale in cui è possibile eseguire il wrapping di una funzione N-arity utilizzando bind per creare un call wrapper con un'arità completamente diversa che potrebbe aggiungere argomenti, rimuoverli, correggerne alcuni a valori limite specifici, ecc.Sarebbe impossibile avere x(1, 2, 3) rilasciare tutti gli argomenti se il comportamento predefinito quando non si utilizzano segnaposti nell'espressione di bind era di inoltrare tutti gli argomenti.

In secondo luogo, è più coerente richiedere sempre di essere espliciti su quali argomenti si desidera passare in quale ordine. In generale, avrebbe senso passare tutti gli argomenti di invocazione quando non ci sono argomenti associati, altrimenti come dovrebbe bind sapere se passare gli argomenti di chiamata prima o dopo gli argomenti associati?

ad es. data

struct X { 
    void f(int, int) { } 
} x; 
auto h = bind(&X::f, &x, 1); 
h(2); 

Se la chiamata a h(2) risultato x.f(1, 2) o x.f(2, 1)? Poiché il comportamento corretto quando ci sono argomenti associati non è ovvio, l'inoltro automatico di tutti gli argomenti quando non ci sono segnaposti usati ha senso solo quando non ci sono argomenti vincolati (perché quindi non c'è alcun dubbio sul fatto che gli argomenti associati debbano venire prima o ultimi) , che è un caso abbastanza speciale. Cambiare una caratteristica significativa dell'API per lavorare con quel caso speciale sarebbe di valore discutibile, specialmente quando rende impossibile il caso x(1, 2, 3) ->foo.bar().

Una soluzione alternativa è quella di continuare che gli utenti debbano essere esplicito su ciò che vogliono, ma fornire un modo esplicito per dire "solo in avanti tutto", come proposto da Tomasz Kamiński in N4171 che sarà discusso in C++ riunione del comitato prossima settimana. Il _all segnaposto risolve il problema di decidere se gli argomenti di invocazione dovrebbero venire prima o dopo gli argomenti legati, perché si può esplicitamente dire se si desidera bind(f, arg1, arg2, std::placeholders::_all) o bind(f, std::placeholders::_all, arg1, arg2) o anche bind(f, arg1, std::placeholders::_all, arg2)

+0

Quindi, cosa ne pensi di diagnosticare le espressioni di binding non valide come un miglioramento QoI? :) – Barry

+1

Buona idea! Lo esaminerò :) –

+0

Il tronco GCC ora controlla l'arità delle espressioni di binding, https://gcc.gnu.org/ml/gcc-patches/2014-11/msg00032.html –

Problemi correlati