Quali sono le ragioni dell'esistenza di std::decay
? In quali situazioni è std::decay
utile?
decay_t<decltype(...)>
è una bella combinazione, per vedere cosa auto
dedurrebbe.
Quali sono le ragioni dell'esistenza di std::decay
? In quali situazioni è std::decay
utile?
decay_t<decltype(...)>
è una bella combinazione, per vedere cosa auto
dedurrebbe.
Risposte:
<joke> È ovviamente usato per decodificare i std::atomic
tipi radioattivi in tipi non radioattivi. </joke>
N2609 è il documento che ha proposto std::decay
. Il documento spiega:
In poche parole,
decay<T>::type
è la trasformazione del tipo di identità, tranne se T è un tipo di array o un riferimento a un tipo di funzione. In questi casidecay<T>::type
restituisce un puntatore o un puntatore a una funzione, rispettivamente.
L'esempio motivante è C ++ 03 std::make_pair
:
template <class T1, class T2>
inline pair<T1,T2> make_pair(T1 x, T2 y)
{
return pair<T1,T2>(x, y);
}
che ha accettato i suoi parametri in base al valore per far funzionare i letterali di stringa:
std::pair<std::string, int> p = make_pair("foo", 0);
Se ha accettato i suoi parametri per riferimento, T1
verrà dedotto come un tipo di array e quindi la costruzione di un pair<T1, T2>
sarà mal formata.
Ma ovviamente questo porta a significative inefficienze. Da qui la necessità decay
di applicare l'insieme di trasformazioni che si verificano quando si verifica il valore pass-by, consentendo di ottenere l'efficienza di prendere i parametri per riferimento, ma ottenere comunque le trasformazioni di tipo necessarie affinché il codice funzioni con valori letterali di stringa, tipi di array, tipi di funzione e simili:
template <class T1, class T2>
inline pair< typename decay<T1>::type, typename decay<T2>::type >
make_pair(T1&& x, T2&& y)
{
return pair< typename decay<T1>::type,
typename decay<T2>::type >(std::forward<T1>(x),
std::forward<T2>(y));
}
Nota: questa non è l' make_pair
implementazione attuale di C ++ 11 - anche il C ++ 11 make_pair
scarterà std::reference_wrapper
s.
Quando si hanno a che fare con funzioni modello che accettano parametri di un tipo di modello, spesso si hanno parametri universali. I parametri universali sono quasi sempre riferimenti di un tipo o di un altro. Sono anche qualificati const-volatile. Pertanto, la maggior parte dei tratti del tipo non funziona su di essi come ci si aspetterebbe:
template<class T>
void func(T&& param) {
if (std::is_same<T,int>::value)
std::cout << "param is an int\n";
else
std::cout << "param is not an int\n";
}
int main() {
int three = 3;
func(three); //prints "param is not an int"!!!!
}
http://coliru.stacked-crooked.com/a/24476e60bd906bed
La soluzione qui è usare std::decay
:
template<class T>
void func(T&& param) {
if (std::is_same<typename std::decay<T>::type,int>::value)
std::cout << "param is an int\n";
else
std::cout << "param is not an int\n";
}
decay
è molto aggressivo, ad esempio se applicato a un riferimento all'array produce un puntatore. In genere è troppo aggressivo per questo tipo di metaprogrammazione IMHO.
remove_const_t< remove_reference_t<T> >
, eventualmente avvolto in una metafunzione personalizzata.