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.