Quais são as razões da existência de std::decay
? Em que situações é std::decay
útil?
decay_t<decltype(...)>
é uma boa combinação, para ver o auto
que deduziria.
Quais são as razões da existência de std::decay
? Em que situações é std::decay
útil?
decay_t<decltype(...)>
é uma boa combinação, para ver o auto
que deduziria.
Respostas:
<joke> Obviamente, é usado para decompor std::atomic
tipos 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>::type
gera 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, T1
será 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_pair
implementação real do C ++ 11 - o C ++ 11 make_pair
também desembrulha std::reference_wrapper
s.
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";
}
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.
remove_const_t< remove_reference_t<T> >
, possivelmente, envolvido em uma metafunção personalizada.