Por que a sobrecarga de um operador de conversão é escolhida?

27

Considere o seguinte código .

struct any
{
    template <typename T>
    operator T &&() const;

    template <typename T>
    operator T &() const;
};
int main()
{
    int a = any{};
}

Aqui, o segundo operador de conversão é escolhido pela resolução de sobrecarga. Por quê?

Tanto quanto eu entendo, os dois operadores são deduzidos operator int &&() conste operator int &() constrespectivamente. Ambos estão no conjunto de funções viáveis. Ler através do [over.match.best] não me ajudou a descobrir por que o último é melhor.

Por que a última função é melhor que a anterior?

avakar
fonte
Eu sou um gato simples e eu diria que deve ser o segundo, mais uma vez a introdução do outro teria sido uma mudança radical no C ++ 11. Estará no padrão em algum lugar. Boa pergunta, porém, tenha um voto positivo. (Peritos Atenção: Se este comentário é besteira, então por favor me diga!)
Bate-Seba
3
FWIW, template <typename T> operator T &&() const &&; template <typename T> operator T &() const &;consegue chamar o primeiro.
NathanOliver 02/12/19
5
Os operadores de conversão @LightnessRaceswithMonica permitem que, caso contrário, não haveria maneira de ter vários operadores de conversão.
NathanOliver
11
@ Bathsheba Eu não acho que essa é a razão. É como dizer que os construtores de movimentação nunca podem ser selecionados por resolução de sobrecarga, porque seria uma mudança de ruptura. Se você escreve um construtor de movimentação, está optando por que seu código seja "quebrado" por essa definição.
Brian
11
@LightnessRaceswithMonica A maneira como eu mantenho isso direto na minha cabeça é tratar o tipo de retorno como o nome da função. Diferentes tipos iguala nomes diferentes é igual :) sucesso
NathanOliver

Respostas:

13

O operador de conversão que retorna T&é o preferido porque é mais especializado que o operador de conversão que retorna T&&.

Veja C ++ 17 [temp.deduct.partial] / (3.2):

No contexto de uma chamada para uma função de conversão, os tipos de retorno dos modelos de função de conversão são usados.

e / 9:

Se, para um determinado tipo, a dedução for bem-sucedida em ambas as direções (ou seja, os tipos são idênticos após as transformações acima) e ambos Pe Aforam tipos de referência (antes de serem substituídos pelo tipo mencionado acima): - se o tipo do modelo de argumento era uma referência lvalue e o tipo do modelo de parâmetro não era, o tipo de parâmetro não é considerado pelo menos tão especializado quanto o tipo de argumento; ...

Brian
fonte
12

Os operadores de conversão de valor de retorno deduzido são um pouco estranhos. Mas a ideia central é que ele funcione como um parâmetro de função para escolher qual deles é usado.

E ao decidir entre T&&e T&as T&vitórias nas regras de resolução de sobrecarga. Isso é para permitir:

template<class T>
void f( T&& ) { std::cout << "rvalue"; }
template<class T>
void f( T& ) { std::cout << "lvalue"; }

trabalhar. T&&pode corresponder a um lvalue, mas quando as sobrecargas de referência lvalue e universal estão disponíveis, o lvalue é o preferido.

O conjunto certo de operadores de conversão é provavelmente:

template <typename T>
operator T&&() &&;

template <typename T>
operator T &() const; // maybe &

ou mesmo

template <typename T>
operator T() &&;

template <typename T>
operator T &() const; // maybe &

para impedir que a extensão da vida útil com falha o morda.

3 Os tipos usados ​​para determinar a ordem dependem do contexto em que a ordem parcial é feita:

[RECORTE]

(3.2) No contexto de uma chamada para uma função de conversão, são usados ​​os tipos de retorno dos modelos de função de conversão.

O que acaba dependendo das regras "mais especializadas" ao selecionar sobrecargas:

(9.1) se o tipo do gabarito de argumento era uma referência lvalue e o tipo do gabarito de parâmetro não era, o tipo de parâmetro não é considerado pelo menos tão especializado quanto o tipo de argumento; de outra forma,

Portanto, operator T&&não é pelo menos tão especializado quanto operator T&, enquanto nenhum estado de regra operator T&não é pelo menos tão especializado quanto operator T&&, portanto, operator T&é mais especializado que operator T&&.

Modelos mais especializados ganham menos que a resolução de sobrecarga, sendo tudo o resto igual.

Yakk - Adam Nevraumont
fonte
Alguém adicionou a etiqueta de advogado de idiomas enquanto eu respondia; Eu poderia simplesmente excluir. Eu tenho uma vaga memória de ler o texto que afirma que ele é tratado como um parâmetro para escolher qual é chamado e o texto que fala sobre como &&vs &é tratado ao escolher sobrecargas de modelo, mas exibir o texto exato levaria um tempo .
Yakk - Adam Nevraumont 02/12/19
4

Estamos tentando inicializar um intde um any. O processo para isso:

  1. Descobrir todo o caminho que podemos fazer isso. Ou seja, determine todos os nossos candidatos. Elas vêm das funções de conversão não explícitas que podem ser convertidas em intuma sequência de conversão padrão ( [over.match.conv] ). A seção contém esta frase:

    Uma chamada para uma função de conversão retornando "referência a X" é um valor gl gl do tipo Xe, portanto, considera-se Xque essa função de conversão produz esse processo de seleção de funções candidatas.

  2. Escolha o melhor candidato.

Após o passo 1, temos dois candidatos. operator int&() conste operator int&&() const, ambos considerados como tendo como intfinalidade a seleção de funções candidatas. Qual é o melhor candidato a render int?

Nós temos um desempatador que prefere referências lvalue a referências rvalue ( [over.ics.rank] / 3.2.2 ). No entanto, não estamos vinculando uma referência aqui, e o exemplo é um pouco invertido - é o caso em que o parâmetro é uma referência lvalue vs rvalue.

Se isso não se aplicar, passamos ao desempate [over.match.best] /2.5 de preferir o modelo de função mais especializado.

De um modo geral, a regra geral é que a conversão mais específica é a melhor correspondência. A função de conversão de referência lvalue é mais específica que a função de conversão de referência de encaminhamento, portanto é preferível. Não há nada na intinicialização que exija um rvalue (se estivéssemos inicializando um int&&, então o operator T&() constnão seria um candidato).

Barry
fonte
3.2.3 realmente diz que T&&vitórias não é? Ah, mas não temos um valor a ser vinculado. Ou nós? Ao escolher nossos candidatos, algumas palavras devem ter definido como chegamos int&e int&&isso foi obrigatório?
Yakk - Adam Nevraumont 02/12/19
@ Yakk-AdamNevraumont Não, isso prefere referências lvalue a referências rvalue. Acho que essa é provavelmente a seção errada de qualquer maneira, e o desempate "mais especializado" é o correto.
Barry