O que é std :: decay e quando deve ser usado?

185

Quais são as razões da existência de std::decay? Em que situações é std::decayútil?

Eric Javier Hernandez Saura
fonte
3
É usado na biblioteca padrão, por exemplo, ao passar argumentos para um thread. Esses precisam ser armazenados , por valor, para que você não possa armazenar, por exemplo, matrizes. Em vez disso, um ponteiro é armazenado e assim por diante. Também é uma metafunção que imita os ajustes do tipo de parâmetro da função.
dyp
3
decay_t<decltype(...)>é uma boa combinação, para ver o autoque deduziria.
Marc Glisse
58
Variáveis ​​radioativas? :)
saiarcot895
7
std :: decay () pode fazer três coisas. 1 É capaz de converter uma matriz de T para T *; 2. Pode remover o qualificador e a referência cv; 3. Converte a função T em T *. por exemplo, decaimento (vazio (caractere)) -> vazio (*) (caractere). Parece que ninguém mencionou o terceiro uso nas respostas.
Rdng 12/17/17
1
Graças a Deus não temos quarks em c ++ ainda
Wormer

Respostas:

192

<joke> Obviamente, é usado para decompor std::atomictipos radioativos em não-radioativos. </joke>

N2609 é o documento que propôs std::decay. O artigo explica:

Simplificando, decay<T>::typeé a transformação do tipo de identidade, exceto se T for um tipo de matriz ou uma referência a um tipo de função. Nesses casos, decay<T>::typegera um ponteiro ou um ponteiro para uma função, respectivamente.

O exemplo motivador é 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 aceitou seus parâmetros por valor para fazer literais de string funcionarem:

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

Se ele aceitou seus parâmetros por referência, T1será deduzido como um tipo de matriz e, em seguida, a construção de um pair<T1, T2>será mal formada.

Mas, obviamente, isso leva a ineficiências significativas. Daí a necessidade de decay, aplicar o conjunto de transformações que ocorre quando passa por valor, permitindo obter a eficiência de obter os parâmetros por referência, mas ainda assim obter as transformações de tipo necessárias para que seu código funcione com literais de string, tipos de matriz, tipos de função e 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: essa não é a make_pairimplementação real do C ++ 11 - o C ++ 11 make_pairtambém desembrulha std::reference_wrappers.

TC
fonte
"T1 será deduzido como um tipo de matriz e, em seguida, a construção de um par <T1, T2> ficará mal formada". Qual é o problema aqui?
Camino 25/05
6
Eu entendo, desta forma teremos par <char [4], int> que só pode aceitar cordas com 4 caracteres
camino
@ camino Eu não entendo, você está dizendo que sem std :: decay a primeira parte do par ocuparia 4 bytes por quatro caracteres em vez de um ponteiro para char? É isso que o std :: forward faz? Impede a decomposição de uma matriz para um ponteiro?
Zebrafish 04/10
3
@ Zebrafish É uma deterioração da matriz. Por exemplo: modelo <nome do tipo T> void f (T &); f ("abc"); T é char (&) [4], mas o modelo <typename T> void f (T); f ("abc"); T é char *; Você também pode encontrar uma explicação aqui: stackoverflow.com/questions/7797839/...
camino
69

Ao lidar com funções de modelo que usam parâmetros de um tipo de modelo, você geralmente possui parâmetros universais. Parâmetros universais são quase sempre referências de um tipo ou de outro. Eles também são qualificados como voláteis. Como tal, a maioria dos traços de tipo não funciona neles conforme o esperado:

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

A solução aqui é 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

Mooing Duck
fonte
14
Eu não estou feliz com isso. decayé muito agressivo, por exemplo, se aplicado a uma referência ao array, gera um ponteiro. É tipicamente agressivo demais para esse tipo de meta-programação IMHO.
dyp
@ dyp, o que é menos "agressivo" então? O que são alternativas?
Serge Rogatch 31/07
5
@SergeRogatch No caso de "parâmetros universais" / referências universais / referências de encaminhamento, eu teria remove_const_t< remove_reference_t<T> >, possivelmente, envolvido em uma metafunção personalizada.
Dip 01/08/19
1
Onde o param está sendo usado? É um argumento de func, mas não o vejo sendo usado em nenhum lugar
savram 17/17
2
@savram: Nestes pedaços de código: não é. Estamos apenas verificando o tipo, não o valor. Tudo deve funcionar bem, se não melhor, se removermos o nome do parâmetro.
Mooing Duck