O código a seguir é compilado com clang-trunk no modo c ++ 17, mas é interrompido no modo c ++ 2a (próximo c ++ 20):
// Meta struct describing the result of a comparison
struct Meta {};
struct Foo {
Meta operator==(const Foo&) {return Meta{};}
Meta operator!=(const Foo&) {return Meta{};}
};
int main()
{
Meta res = (Foo{} != Foo{});
}
Também compila bem com gcc-trunk ou clang-9.0.0: https://godbolt.org/z/8GGT78
O erro com clang-trunk e -std=c++2a
:
<source>:12:19: error: use of overloaded operator '!=' is ambiguous (with operand types 'Foo' and 'Foo')
Meta res = (f != g);
~ ^ ~
<source>:6:10: note: candidate function
Meta operator!=(const Foo&) {return Meta{};}
^
<source>:5:10: note: candidate function
Meta operator==(const Foo&) {return Meta{};}
^
<source>:5:10: note: candidate function (with reversed parameter order)
Eu entendo que o C ++ 20 tornará possível apenas sobrecarregar operator==
e o compilador será gerado automaticamente operator!=
negando o resultado de operator==
. Tanto quanto eu entendo, isso só funciona enquanto o tipo de retorno é bool
.
A fonte do problema é que em Eigen que declarar um conjunto de operadores ==
, !=
, <
, ... entre Array
objectos ou Array
e escalares, que retornam (uma expressão de) uma matriz de bool
(a qual pode então ser acedida elemento a elemento, ou de outro modo utilizados ) Por exemplo,
#include <Eigen/Core>
int main()
{
Eigen::ArrayXd a(10);
a.setRandom();
return (a != 0.0).any();
}
Em contraste com o meu exemplo acima, isso ainda falha com o gcc-trunk: https://godbolt.org/z/RWktKs . Ainda não consegui reduzir isso a um exemplo que não é Eigen, que falha nos clang-trunk e no gcc-trunk (o exemplo na parte superior é bastante simplificado).
Relatório de problema relacionado: https://gitlab.com/libeigen/eigen/issues/1833
Minha pergunta real: isso é realmente uma mudança de quebra no C ++ 20 (e existe a possibilidade de sobrecarregar os operadores de comparação para retornar Meta-objetos) ou é mais provável uma regressão no clang / gcc?
Respostas:
O problema de Eigen parece reduzir ao seguinte:
Os dois candidatos à expressão são
operator==(const Scalar&, const Derived&)
Base<X>::operator!=(const Scalar&) const
Por [over.match.funcs] / 4 , como
operator!=
não foi importado para o escopoX
por uma declaração de uso , o tipo do parâmetro implícito do objeto para # 2 éconst Base<X>&
. Como resultado, o número 1 tem uma sequência de conversão implícita melhor para esse argumento (correspondência exata, em vez da conversão de derivada para base). A seleção de # 1 torna o programa mal formado.Correções possíveis:
using Base::operator!=;
aDerived
ouoperator==
para tirar um emconst Base&
vez de aconst Derived&
.fonte
bool
delesoperator==
? Porque essa parece ser a única razão pela qual o código está mal formado sob as novas regras.operator==(Array, Scalar)
que faz comparação entre elementos e retorna umArray
debool
. Você não pode transformar isso em umbool
sem quebrar todo o resto.operator==
não deveriam afetar o código existente, no entanto, nesse caso, porque a verificação de umbool
valor de retorno não faz parte da seleção de candidatos para reescrita.Sim, o código de fato quebra no C ++ 20.
A expressão
Foo{} != Foo{}
possui três candidatos no C ++ 20 (considerando que havia apenas um no C ++ 17):Isso vem das novas regras de candidato reescritas em [over.match.oper] /3.4 . Todos esses candidatos são viáveis, já que nossos
Foo
argumentos não sãoconst
. Para encontrar o melhor candidato viável, precisamos passar por nossos desempates.As regras relevantes para a melhor função viável são, de [over.match.best] / 2 :
#2
e#3
são candidatos reescritos e#3
inverte a ordem dos parâmetros, enquanto#1
não é reescrito. Mas, para chegar a esse desempatador, precisamos primeiro passar pela condição inicial: para todos os argumentos, as seqüências de conversão não são piores.#1
é melhor do que#2
porque todas as seqüências de conversão são iguais (trivialmente, porque os parâmetros de função são os mesmos) e#2
é um candidato reescrito enquanto#1
não é.Mas ... ambos os pares
#1
/#3
e#2
/#3
ficam presos nessa primeira condição. Nos dois casos, o primeiro parâmetro tem uma melhor sequência de conversão para#1
/#2
enquanto o segundo parâmetro tem uma melhor sequência de conversão para#3
(o parâmetroconst
precisa passar por umaconst
qualificação extra , para ter uma pior sequência de conversão). Esteconst
flip-flop faz com que não possamos preferir nenhum deles.Como resultado, toda a resolução de sobrecarga é ambígua.
Isso não está correto. Consideramos incondicionalmente candidatos reescritos e revertidos. A regra que temos é, de [over.match.oper] / 9 :
Ou seja, ainda consideramos esses candidatos. Mas se o melhor candidato viável é
operator==
aquele que retorna, digamos,Meta
- o resultado é basicamente o mesmo que se esse candidato tivesse sido excluído.Nós fez não quer estar em um estado onde a resolução de sobrecarga teria que considerar o tipo de retorno. E, de qualquer forma, o fato de o código retornar aqui
Meta
é irrelevante - o problema também existiria se ele retornassebool
.Felizmente, a correção aqui é fácil:
Depois de criar os dois operadores de comparação
const
, não há mais ambiguidade. Todos os parâmetros são iguais, portanto, todas as seqüências de conversão são trivialmente iguais.#1
agora venceria#3
por não ser reescrita e#2
agora venceria#3
por não ser revertida - o que torna#1
o melhor candidato viável. O mesmo resultado que tivemos no C ++ 17, apenas mais algumas etapas para chegar lá.fonte
==
e o tipo de retorno da função selecionada nãobool
. Mas esse abate não ocorre durante a própria resolução de sobrecarga.cv bool
(e antes dessa alteração, o requisito era de conversão contextual parabool
- ainda não!
)[over.match.best] / 2 lista como as sobrecargas válidas em um conjunto são priorizadas. A Seção 2.8 nos diz que
F1
é melhor do queF2
se (entre muitas outras coisas):O exemplo lá mostra um explícito
operator<
sendo chamado mesmo queoperator<=>
esteja lá.E [over.match.oper] /3.4.3 nos diz que a candidatura
operator==
nessas circunstâncias é um candidato reescrito.No entanto , seus operadores esquecem uma coisa crucial: eles devem ser
const
funções. E fazê-los nãoconst
faz com que aspectos anteriores da resolução de sobrecarga entrem em cena. Nem função é uma correspondência exata, como nãoconst
Para-const
conversões precisam acontecer para diferentes argumentos. Isso causa a ambiguidade em questão.Depois de criá -los
const
, o Clang tronco é compilado .Não posso falar com o resto de Eigen, pois não conheço o código, é muito grande e, portanto, não pode caber em um MCVE.
fonte
const
, os candidatos não revertidos têm uma melhor sequência de conversão para o segundo argumento e o candidato revertido tem uma melhor sequência de conversão para o primeiro argumento.const
exemplo mínimo. Tenho certeza de que o Eigen usa emconst
todos os lugares (ou fora das definições de classe, também comconst
referências), mas preciso verificar. Eu tento quebrar o mecanismo geral que Eigen usa para um exemplo mínimo, quando encontro o tempo.Temos problemas semelhantes com nossos arquivos de cabeçalho Goopax. Compilar o seguinte com clang-10 e -std = c ++ 2a produz um erro do compilador.
Fornecer esses operadores adicionais parece resolver o problema:
fonte
a == 0
sido compilado ?gpu_bool gpu_type<T>::operator==(T a) const;
egpu_bool gpu_type<T>::operator!=(T a) const;
com o C ++ - 17, isso funciona bem. Mas agora com clang-10 e C ++ - 20, eles não são mais encontrados e, em vez disso, o compilador tenta gerar seus próprios operadores trocando os argumentos e falha, porque o tipo de retorno não ébool
.