Cos'è std :: decay e quando dovrebbe essere usato?


185

Quali sono le ragioni dell'esistenza di std::decay? In quali situazioni è std::decayutile?


3
Viene utilizzato nella libreria standard, ad es. Quando si passano argomenti a un thread. Questi devono essere memorizzati , in base al valore, quindi non è possibile memorizzare ad es. Array. Invece, viene memorizzato un puntatore e così via. È anche una metafunzione che imita le regolazioni del tipo di parametro della funzione.
— dyp

3
decay_t<decltype(...)>è una bella combinazione, per vedere cosa autodedurrebbe.
— Marc Glisse,

58
Variabili radioattive? :)
— saiarcot895

7
std :: decay () può fare tre cose. 1 È in grado di convertire un array di T in T *; 2. Può rimuovere il qualificatore e il riferimento del cv; 3. Converte la funzione T in T *. es. decadimento (void (char)) -> void (*) (char). Sembra che nessuno abbia menzionato il terzo utilizzo nelle risposte.
— r0ng,

1
Per fortuna non abbiamo ancora quark in c ++
— Wormer

Risposte:


192

<joke> È ovviamente usato per decodificare i std::atomictipi 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 casi decay<T>::typerestituisce 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, T1verrà 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à decaydi 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_pairimplementazione attuale di C ++ 11 - anche il C ++ 11 make_pairscarterà std::reference_wrappers.


"T1 verrà dedotto come un tipo di array e quindi la costruzione di una coppia <T1, T2> non sarà corretta." qual è il problema qui?
— camino,

6
Capisco, in questo modo otterremo la coppia <char [4], int> che può accettare solo stringhe con 4 caratteri
— camino

@camino Non capisco, stai dicendo che senza std :: decadimento la prima parte della coppia occuperebbe 4 byte per quattro caratteri anziché un solo puntatore a carattere? È quello che fa lo std :: forward? Impedisce il decadimento da un array a un puntatore?
— Zebrafish

3
@Zebrafish È un decadimento di array. Ad esempio: template <typename T> void f (T &); f ( "abc"); T è char (&) [4], ma template <typename T> void f (T); f ( "abc"); T è char *; Puoi anche trovare una spiegazione qui: stackoverflow.com/questions/7797839/…
— camino

69

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";
}

http://coliru.stacked-crooked.com/a/8cbd0119a28a18bd


14
Non sono contento di questo. 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.
— dyp

@dyp, cos'è meno "aggressivo" allora? Quali sono le alternative?
— Serge Rogatch,

5
@SergeRogatch Nel caso di "parametri universali" / riferimenti universali / riferimenti di inoltro, avrei semplicemente remove_const_t< remove_reference_t<T> >, eventualmente avvolto in una metafunzione personalizzata.
— dyp

1
dove viene utilizzato anche param? È un argomento di func, ma non lo vedo usato da nessuna parte
— savram,

2
@savram: in queste parti di codice: non lo è. Stiamo solo verificando il tipo, non il valore. Tutto dovrebbe funzionare bene se non meglio se rimuovessimo il nome del parametro.
— Mooing Duck,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.