¿Qué es std :: decay y cuándo debe usarse?

185

¿Cuáles son las razones de la existencia de std::decay? ¿En qué situaciones es std::decayútil?

Eric Javier Hernández Saura
fuente
3
Se utiliza en la biblioteca estándar, por ejemplo, al pasar argumentos a un hilo. Es necesario almacenarlos , por valor, para que no pueda almacenar, por ejemplo, matrices. En cambio, se almacena un puntero y así sucesivamente. También es una metafunción que imita los ajustes de tipo de parámetro de función.
dyp
3
decay_t<decltype(...)>es una buena combinación, para ver qué autodeduciría.
Marc Glisse
58
Variables radiactivas? :)
saiarcot895
77
std :: decay () puede hacer tres cosas. 1 Puede convertir una matriz de T a T *; 2. Puede eliminar el calificador cv y la referencia; 3. Convierte la función T en T *. Por ejemplo, decay (void (char)) -> void (*) (char). Parece que nadie mencionó el tercer uso en las respuestas.
r0ng
1
Menos mal que todavía no tenemos quarks en c ++
Wormer

Respuestas:

192

<joke> Obviamente se usa para descomponer los std::atomictipos radiactivos en no radiactivos. </joke>

N2609 es el documento que propuso std::decay. El artículo explica:

En pocas palabras, decay<T>::typees la transformación del tipo de identidad, excepto si T es un tipo de matriz o una referencia a un tipo de función. En esos casos, se decay<T>::typeobtiene un puntero o un puntero a una función, respectivamente.

El ejemplo motivador es 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); 
}

que aceptó sus parámetros por valor para hacer que los literales de cadena funcionen:

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

Si aceptó sus parámetros por referencia, T1se deducirá como un tipo de matriz, y luego la construcción de un pair<T1, T2>estará mal formada.

Pero obviamente esto conduce a ineficiencias significativas. De ahí la necesidad de decayaplicar el conjunto de transformaciones que ocurre cuando ocurre el paso por valor, lo que le permite obtener la eficiencia de tomar los parámetros por referencia, pero aún obtener las transformaciones de tipo necesarias para que su código funcione con literales de cadena, tipos de matriz, tipos de funciones y similares:

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: esta no es la make_pairimplementación real de C ++ 11 : el C ++ 11 make_pairtambién desenvuelve std::reference_wrappers.

TC
fuente
"T1 se deducirá como un tipo de matriz, y luego construir un par <T1, T2> estará mal formado". ¿Cuál es el problema aquí?
camino
66
Lo entiendo, de esta manera obtendremos un par <char [4], int> que solo puede aceptar cadenas con 4 caracteres
camino
@camino No lo entiendo, ¿estás diciendo que sin std :: decay la primera parte del par ocuparía 4 bytes para cuatro caracteres en lugar de un puntero a char? ¿Es eso lo que hace std :: forward? ¿Evita que se descomponga de una matriz a un puntero?
Pez cebra
3
@ Zebrafish Es la descomposición de la matriz. Por ejemplo: template <typename T> void f (T &); f ("abc"); T es char (&) [4], pero la plantilla <typename T> void f (T); f ("abc"); T es char *; También puede encontrar una explicación aquí: stackoverflow.com/questions/7797839/…
camino
69

Cuando se trata de funciones de plantilla que toman parámetros de un tipo de plantilla, a menudo tiene parámetros universales. Los parámetros universales son casi siempre referencias de un tipo u otro. También están calificados con volatilidad constante. Como tal, la mayoría de los rasgos tipográficos no funcionan en ellos como cabría esperar:

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 solución aquí es usar 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

Pato mugido
fuente
14
No estoy contento con esto. decayes muy agresivo, por ejemplo, si se aplica a una referencia a una matriz, produce un puntero. Por lo general, es demasiado agresivo para este tipo de metaprogramación en mi humilde opinión.
dyp
@dyp, ¿qué es menos "agresivo" entonces? ¿Qué son las alternativas?
Serge Rogatch
55
@SergeRogatch En el caso de "parámetros universales" / referencias universales / referencias de reenvío, simplemente remove_const_t< remove_reference_t<T> >, posiblemente, envuelto en una metafunción personalizada.
dyp
1
¿Dónde se está usando param? Es un argumento func, pero no veo que se use en ningún lado
savram
2
@savram: en estos fragmentos de código: no lo es. Solo estamos verificando el tipo, no el valor. Todo debería funcionar bien, si no mejor, si eliminamos el nombre del parámetro.
Mooing Duck