Como evitar essa frase é falso em um modelo SFINAE?
Então eu quero escrever um automático!=
:
template<typename U, typename T>
bool operator!=(U&& u, T&& t) {
return !( std::forward<U>(u) == std::forward<T>(t) );
}
mas isso é indelicado1. Então eu escrevo
// T() == U() is valid?
template<typename T, typename U, typename=void>
struct can_equal:std::false_type {};
template<typename T, typename U>
struct can_equal<
T,
U,
typename std::enable_if<
std::is_convertible<
decltype( std::declval<T>() == std::declval<U>() ),
bool
>::value
>::type
>: std::true_type {};
que é uma classe de traços do tipo que diz "ét == u
código válido que retorna um tipo conversível parabool
".
Então eu melhorei meu!=
:
template<typename U, typename T,
typename=typename std::enable_if<can_equal<T,U>::value>::type
>
bool operator!=(U&& u, T&& t) {
return !( std::forward<U>(u) == std::forward<T>(t) );
}
e agora só é uma substituição válida se==
existe. Infelizmente, é um pouco ganancioso:
struct test {
};
bool operator==(const test&, const test&);
bool operator!=(const test&, const test&);
como vai snarf praticamente todos ostest() != test()
em vez do acima!=
sendo chamado. Eu acho que isso não é desejado - eu prefiro chamar um!=
de auto-forward para==
e negar.
Então, eu escrevo esta classe de traços:
template<typename T, typename U,typename=void>
struct can_not_equal // ... basically the same as can_equal, omitted
que testa seT != U
é válido.
Nós então aumentamos o!=
do seguinte modo:
template<typename U, typename T,
typename=typename std::enable_if<
can_equal<T,U>::value
&& !can_not_equal<T,U>::value
>::type
>
bool operator!=(U&& u, T&& t) {
return !( std::forward<U>(u) == std::forward<T>(t) );
}
que, se você analisar, diz "esta frase é falsa" -operator!=
existe entreT
eU
se assimoperator!=
não existe entreT
eU
.
Não é de surpreender que todos os compiladores tenham testado os segfaults quando os alimentaram. (clang 3.2, gcc 4.8 4.7.2 intel 13.0.1).Eu suspeito que o que estou fazendo é ilegal, mas eu adoraria ver a referência padrão. (editar: O que estou fazendo é ilegal, porque induz uma expansão de modelo recursiva ilimitada, determinando se meu!=
aplica-se requer que verifiquemos se o meu!=
aplica-se. A versão vinculada nos comentários, com#if 1
, dá um erro sensato).
Mas a minha pergunta: existe alguma maneira de eu convencer minha substituição baseada na SFINAE a ignorar "ela mesma" ao decidir se ela deve falhar ou não, ou de alguma forma se livrar da questão de auto-referência de alguma forma? Ou abaixe a precedência do meuoperator!=
baixo o suficiente para qualquer explícito!=
vence, mesmo que não seja tão bom como um jogo?
Aquele que não verifica "!=
não existe "funciona razoavelmente bem, mas não o suficiente para eu ser tão indelicado quanto injetá-lo no espaço de nomes global.
O objetivo é qualquer código que compile sem minha "mágica"!=
faz exatamente a mesma coisa uma vez minha "mágica"!=
é introduzido. Se e apenas se!=
é de outra forma inválidoe bool r = !(a==b)
está bem formado deve minha "mágica"!=
chutar.
Nota de rodapé1: Se você criar umtemplate<typename U, typename T> bool operator!=(U&& u, T&& t)
, SFINAE vai pensar que cada par de tipos tem um válido!=
entre eles. Então, quando você tenta realmente ligar!=
, é instanciado e não consegue compilar. Além disso, você pisa embool operator!=( const foo&, const foo& )
funções, porque você é um jogo melhor parafoo() != foo()
efoo a, b; a != b;
. Eu considero fazer essas duas coisas indelicadas.