17

Ho una funzione che accetta un parametro con un valore predefinito. Ora voglio anche prendere un numero variabile di parametri e inoltrarli ad un'altra funzione. I parametri di funzione con valore di default devono essere gli ultimi, quindi ... posso mettere quel parametro dopo il variadic pack e il compilatore rileverà se lo sto fornendo o meno quando chiama la funzione?Parametro variadic template C++ con valore predefinito

(Presumendo che il pacchetto non contenga il tipo di quell'ultimo parametro. Se necessario, possiamo supporre che, in genere, questo tipo non sia noto all'utente, altrimenti è considerato un uso errato del mio interfaccia in ogni caso ....)

template <class... Args> 
void func (Args&&... args, SomeSpecialType num = fromNum(5)) 
{ 
} 

risposta

16

No, i pacchetti devono essere scaduti.

Ma puoi fingere. È possibile rilevare quale sia l'ultimo tipo in un pacchetto. Se è SomeSpecialType, puoi eseguire la tua funzione. Se non è SomeSpecialType, puoi chiamarti in modo ricorsivo con i tuoi argomenti inoltrati e fromNum(5) aggiunto.

Se si vuole essere fantasiosi, questo controllo può essere fatto in fase di compilazione (cioè un sovraccarico diverso) utilizzando tecniche SFINAE. Ma probabilmente non ne vale la pena, considerando che il controllo di "run-time" sarà costante su un dato sovraccarico, e quindi sarà quasi certamente ottimizzato, e SFINAE non dovrebbe essere usato alla leggera.

Questo non ti dà la firma che desideri, ma ti dà il comportamento che desideri. Dovrai spiegare la firma prevista nei commenti.

Qualcosa del genere, dopo la rimozione errori di battitura e simili:

// extract the last type in a pack. The last type in a pack with no elements is 
// not a type: 
template<typename... Ts> 
struct last_type {}; 
template<typename T0> 
struct last_type<T0> { 
    typedef T0 type; 
}; 
template<typename T0, typename T1, typename... Ts> 
struct last_type<T0, T1, Ts...>:last_type<T1, Ts...> {}; 

// using aliases, because typename spam sucks: 
template<typename Ts...> 
using LastType = typename last_type<Ts...>::type; 
template<bool b, typename T=void> 
using EnableIf = typename std::enable_if<b, T>::type; 
template<typename T> 
using Decay = typename std::decay<T>::type; 

// the case where the last argument is SomeSpecialType: 
template< 
    typename... Args, 
    typename=EnableIf< 
    std::is_same< 
     Decay<LastType<Args...>>, 
     SomeSpecialType 
    >::value 
    > 
void func(Args&&... args) { 
    // code 
} 

// the case where there is no SomeSpecialType last:  
template< 
    typename... Args, 
    typename=EnableIf< 
    !std::is_same< 
     typename std::decay<LastType<Args...>>::type, 
     SomeSpecialType 
    >::value 
    > 
void func(Args&&... args) { 
    func(std::forward<Args>(args)..., std::move(static_cast<SomeSpecialType>(fromNum(5)))); 
} 

// the 0-arg case, because both of the above require that there be an actual 
// last type: 
void func() { 
    func(std::move(static_cast<SomeSpecialType>(fromNum(5)))); 
} 

o qualcosa di molto simile.

+0

Quindi è come una soluzione alternativa, è una firma diversa ma lo stesso comportamento ... Vedo. In realtà stavo progettando di rimuovere quel parametro in futuro, quindi forse non ne vale la pena (e la firma sarebbe confusa). Puoi mostrarmi un semplice esempio? – cfa45ca55111016ee9269f0a52e771

+0

@ fr33domlover Ho abbozzato il disegno. Non è stato compilato, per non parlare del debug, ma i fondamenti dovrebbero essere lì. – Yakk

+0

Grazie, proverò se non deciderò semplicemente di rimuovere il singolo parametro. Sembra complicato e la firma non viene mantenuta, quindi potrebbe non valerne la pena ... comunque grazie – cfa45ca55111016ee9269f0a52e771

3

Un altro approccio sarebbe quello di passare argomenti variadici attraverso una tupla.

template <class... Args> 
void func (std::tuple<Args...> t, SomeSpecialType num = fromNum(5)) 
{ 
    // don't forget to move t when you use it for the last time 
} 

Pro: l'interfaccia è molto più semplice, l'overloading e l'aggiunta di argomenti con valori predefiniti è abbastanza semplice.

Contro: il chiamante deve inserire manualmente gli argomenti in una chiamata std::make_tuple o std::forward_as_tuple. Inoltre, probabilmente dovrai ricorrere ai trucchi std::index_sequence per implementare la funzione.

Problemi correlati