Qu'est-ce que std :: decay et quand doit-il être utilisé?

185

Quelles sont les raisons de l'existence de std::decay? Dans quelles situations est-il std::decayutile?

Eric Javier Hernandez Saura
la source
3
Il est utilisé dans la bibliothèque standard, par exemple lors du passage d'arguments à un thread. Ceux-ci doivent être stockés , par valeur, vous ne pouvez donc pas stocker par exemple des tableaux. Au lieu de cela, un pointeur est stocké et ainsi de suite. C'est également une métafonction qui imite les ajustements de type de paramètre de fonction.
dyp
3
decay_t<decltype(...)>est une belle combinaison, pour voir ce que l' autoon en déduit.
Marc Glisse
58
Variables radioactives? :)
saiarcot895
7
std :: decay () peut faire trois choses. 1 Il est capable de convertir un tableau de T en T *; 2. Il peut supprimer le qualificatif et la référence cv; 3. Il convertit la fonction T en T *. par exemple decay (void (char)) -> void (*) (char). Il semble que personne n'ait mentionné le troisième usage dans les réponses.
R0ng
1
Dieu merci, nous n'avons pas encore de quarks en c ++
Wormer

Réponses:

192

<joke> Il est évidemment utilisé pour décomposer les std::atomictypes radioactifs en types non radioactifs. </joke>

N2609 est le papier qui a proposé std::decay. Le papier explique:

En termes simples, decay<T>::typeest la transformation du type d'identité sauf si T est un type de tableau ou une référence à un type de fonction. Dans ces cas, le decay<T>::typeproduit un pointeur ou un pointeur vers une fonction, respectivement.

L'exemple motivant est 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); 
}

qui a accepté ses paramètres par valeur pour faire fonctionner les littéraux de chaîne:

std::pair<std::string, int> p = make_pair("foo", 0);

S'il a accepté ses paramètres par référence, alors T1sera déduit comme un type de tableau, puis la construction de a pair<T1, T2>sera mal formée.

Mais cela conduit évidemment à des inefficacités significatives. D'où la nécessité decay, d'appliquer l'ensemble des transformations qui se produisent lorsque le passage par valeur se produit, vous permettant d'obtenir l'efficacité de prendre les paramètres par référence, tout en obtenant les transformations de type nécessaires pour que votre code fonctionne avec des littéraux de chaîne, types de tableaux, types de fonctions et autres:

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

Remarque: ce n'est pas l' make_pairimplémentation réelle de C ++ 11 - le C ++ 11 make_pairdéballe également std::reference_wrappers.

TC
la source
"T1 sera déduit comme un type de tableau, puis la construction d'une paire <T1, T2> sera mal formée." Quel est le problème ici?
camino
6
Je comprends, de cette façon nous obtiendrons la paire <char [4], int> qui ne peut accepter que des chaînes de 4 caractères
camino
@camino Je ne comprends pas, est-ce que vous dites que sans std :: decay, la première partie de la paire occuperait 4 octets pour quatre caractères au lieu d'un pointeur vers char? Est-ce ce que fait std :: forward? Empêche-t-il de se désintégrer d'un tableau à un pointeur?
Poisson zèbre
3
@Zebrafish C'est la désintégration du tableau. Par exemple: template <typename T> void f (T &); f ("abc"); T est char (&) [4], mais template <typename T> void f (T); f ("abc"); T est char *; Vous pouvez également trouver une explication ici: stackoverflow.com/questions/7797839/…
camino
69

Lorsque vous traitez avec des fonctions de modèle qui prennent des paramètres d'un type de modèle, vous avez souvent des paramètres universels. Les paramètres universels sont presque toujours des références d'une sorte ou d'une autre. Ils sont également qualifiés pour la volatilité constante. En tant que tel, la plupart des traits de type ne fonctionnent pas sur eux comme vous vous en doutez:

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 solution ici est d'utiliser 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

Canard meunier
la source
14
Je ne suis pas content de ça. decayest très agressif, par exemple s'il est appliqué à une référence à un tableau, il produit un pointeur. Il est généralement trop agressif pour ce type de métaprogrammation à mon humble avis.
dyp
@dyp, qu'est-ce qui est moins "agressif" alors? Quelles sont les alternatives?
Serge Rogatch
5
@SergeRogatch Dans le cas de "paramètres universels" / références universelles / références de transfert, je serais juste remove_const_t< remove_reference_t<T> >, éventuellement enveloppé dans une métafonction personnalisée.
dyp
1
où param est-il même utilisé? C'est un argument de func mais je ne le vois pas utilisé nulle part
savram
2
@savram: Dans ces morceaux de code: ce n'est pas le cas. Nous vérifions uniquement le type, pas la valeur. Tout devrait fonctionner correctement sinon mieux si nous supprimons le nom du paramètre.
Mooing Duck