Por que essa função retorna uma referência lvalue, dados os argumentos rvalue?
A seguinte definição demin
função
template <typename T, typename U>
constexpr auto
min(T&& t, U&& u) -> decltype(t < u ? t : u)
{
return t < u ? t : u;
}
tem um problema: parece perfeitamente legal escrever
min(10, 20) = 0;
Isso foi testado com o Clang 3.5 e g ++ 4.9.
A solução é simples, basta usarstd::forward
para restaurar o "valor-valor" dos argumentos, ou seja, modificar o corpo e odecltype
dizer
t < u ? std::forward<T>(t) : std::forward<U>(u)
No entanto, não tenho como explicarporque a primeira definição não gera um erro.
Dado meu entendimento de encaminhamento e referências universais, ambost
eu
deduzir seus tipos de argumento comoint&&
quando passados literais inteiros. No entanto, dentro do corpo demin
, os argumentos têm nomes e, portanto, são lvalues. Agora oregras realmente complicadas para o operador condicional entram em jogo, mas eupensar a linha pertinente é:
e, portanto, o tipo de retorno deoperator?:
deveria estarint&&
também, não deveria? No entanto, (até onde eu sei), tanto o Clang quanto o g ++ têmmin(int&&, int&&)
retornando uma referência lvalueint&
, permitindo-me atribuir ao resultado.
Claramente, há uma lacuna no meu entendimento, mas não tenho certeza exatamente do que estou perdendo. Alguém pode me explicar exatamente o que está acontecendo aqui?
EDITAR:
Como Niall corretamente aponta, o problema aqui não é com o operador condicional (que está retornando um lvalue do tipoint&&
conforme o esperado), mas com odecltype
. As regras paradecltype
dizer
se a categoria de valor da expressão for lvalue, o tipo de declínio especificará T &
então o valor de retorno da função se tornaint&& &
, que pelas regras de recolhimento de referência do C ++ 11 se transforma em simplesint&
(ao contrário do que eu esperavaint&&
)
Mas se usarmosstd::forward
, vimos o segundo e o terceiro argumentos deoperator?:
(voltar) em rvalues - especificamente, xvalues. Como xvalues ainda são glvalues (você está acompanhando?), A mesma regra de operador condicional se aplica, e obtemos um resultado da mesma categoria de tipo e valor: ou seja, umint&&
que é um valor x.
Agora, quando a função retorna, ela dispara uma função diferente.decltype
regra:
se a categoria de valor da expressão for xvalue, o tipo de declínio especificará T&&
Desta vez, o colapso de referência nos dáint&& && = int&&
e, mais importante, a função retorna um valor x. Isso torna ilegal atribuir ao valor de retorno, como gostaríamos.