2015-06-21 23 views
5

Il seguente codice non può essere compilato:std :: bind e spedizioni perfetto

#include <functional> 

template<class ...Args> 
void invoke(Args&&... args) 
{ 
} 

template<class ...Args> 
void bind_and_forward(Args&&... args) 
{ 
    auto binder = std::bind(&invoke<Args...>, std::forward<Args>(args)...); 
    binder(); 
} 

int main() 
{ 
    int a = 1; 
    bind_and_forward(a, 2); 
} 

Se ho capito bene, la ragione è la seguente: std::bind copia i suoi argomenti, e quando s' il binderoperator() si chiama, passa tutti gli argomenti associati come lvalues ​​ - anche quelli che sono entrati in bind come rvalues ​​. Ma invoke è stato istanziato per gli argomenti originali e non può accettare ciò che lo binder tenta di passarlo.

Esiste una soluzione per questo problema?

risposta

4

La tua comprensione è corretta - bind copia i suoi argomenti. Quindi, è necessario fornire la corretta sovraccarico invoke() che sarebbe stato chiesto alle lvalue:

template<class ...Args> 
void bind_and_forward(Args&&... args) 
{ 
    auto binder = std::bind(&invoke<Args&...>, std::forward<Args>(args)...); 
            ^^^^^^^^ 
    binder(); 
} 

Questo funziona sulla maggior parte dei tipi. Ci sono alcune eccezioni elencate in [func.bind.bind] per operator(), dove Arg& non è sufficiente. Uno di questi, come si fa notare, è std::reference_wrapper<T>. Possiamo aggirare questo sostituendo l'uso di Args& in precedenza con un carattere di tipo. In genere, avevamo appena aggiungiamo un riferimento lvalue, ma per reference_wrapper<T>, vogliamo solo T&:

template <typename Arg> 
struct invoke_type 
: std::add_lvalue_reference<Arg> { }; 

template <typename T> 
struct invoke_type<std::reference_wrapper<T>> { 
    using type = T&; 
}; 

template <typename T> 
using invoke_type_t = typename invoke_type<T>::type; 

Plug che di nuovo nella soluzione originale, e otteniamo qualcosa che funziona per reference_wrapper troppo:

template<class ...Args> 
void bind_and_forward(Args&&... args) 
{ 
    auto binder = std::bind(&invoke<invoke_type_t<Args>...>, 
          //  ^^^^^^^^^^^^^^^^^^^ 
          std::forward<Args>(args)...); 
    binder(); 
} 

Ovviamente, se uno di Arg è un segnaposto, questo non funzionerà comunque. E se è un'espressione vincolante, dovrai scrivere anche qualcos'altro.

+0

Ma in questo caso non verrà compilato per un argomento incluso con 'std :: ref()'. –

+0

@IgorR. Aggiornato per quello - 'bind()' fa qualcosa di speciale per 'std :: ref()'. Quindi dovresti fare anche qualcosa di speciale. – Barry