c++ - ¿Cómo evitar que esta oración sea falsa en una plantilla de SFINAE?
templates c++11 (1)
El problema con su enfoque parece ser que la definición global alternativa de operator !=
Es demasiado atractiva, y necesita un cheque SFINAE para descartarlo. Sin embargo, la verificación SFINAE depende de la elegibilidad de la función en sí misma para la resolución de sobrecarga, lo que lleva a una (repetida) tentativa infinita durante la deducción de tipo.
Me parece que cualquier intento similar basado en SFINAE chocaría contra el mismo muro, por lo que el enfoque más sensato es, en mi opinión, hacer que su operator !=
un poco menos atractivo para la resolución de sobrecargas en primer lugar, y deje que otros, razonablemente escrito (esto quedará claro en un momento) sobrecargas de operator !=
tienen prioridad.
Dado el rasgo de tipo can_equal
que proporcionaste:
#include <type_traits>
#include <functional>
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 {};
Definiría el operator !=
reserva operator !=
esta manera:
template<typename T, typename U>
bool is_not_equal(T&& t, U&& u)
{
return !(std::forward<T>(t) == std::forward<U>(u));
}
template<
typename T,
typename... Ts,
typename std::enable_if<can_equal<T, Ts...>::value>::type* = nullptr
>
bool operator != (T const& t, Ts const&... args)
{
return is_not_equal(t, args...);
}
Por lo que sé, cualquier sobrecarga de operator !=
Que definirá exactamente dos parámetros de función (por lo que no habrá un paquete de argumentos) será mejor para la resolución de sobrecarga. Por lo tanto, la versión anterior del operator !=
Se seleccionará solo cuando no exista una sobrecarga mejor. Además, se seleccionará solo si el rasgo de tipo can_equal<>
devolverá true
.
He probado esto contra el SSCCE que preparó, donde se definen cuatro struct
junto con algunas sobrecargas de operator ==
y operator !=
:
struct test { };
bool operator==(const test&, const test&) { std::cout << "(==)"; return true; }
bool operator!=(const test&, const test&) { std::cout << "(!==)"; return true; }
struct test2 { };
struct test3 { };
bool operator == (const test3&, const test3&)
{ std::cout << "(==)"; return true; }
struct test4 { };
template<typename T,
EnableIf< std::is_convertible< T, test4 const& >::value >... >
bool operator == ( T&&, T&& ) { std::cout << "(==)"; return true; }
template<typename T,
EnableIf< std::is_convertible< T, test4 const& >::value >... >
bool operator != ( T&&, T&& ) { std::cout << "(!=)"; return true; }
Para verificar que se produzca la salida deseada y reflejar lo que hizo en su versión original del operator !=
respaldo operator !=
, is_not_equal()
una impresión a is_not_equal()
:
template<typename T, typename U>
bool is_not_equal(T&& t, U&& u)
{
std::cout << "!"; // <== FOR TESTING PURPOSES
return !(std::forward<T>(t) == std::forward<U>(u));
}
Aquí están las tres pruebas de su ejemplo:
std::cout << (a != b) << "/n"; // #1
std::cout << (test3() != test3()) << "/n"; // #2
std::cout << (test4() != test4()) << "/n"; // #3
Con respecto a la primera prueba, el operator !=
Se define para la test
tipo, por lo que la línea #1
debe imprimir:
(!==)1
Con respecto a la segunda prueba, operator !=
No está definido para test3
, y test3
no se puede convertir a test4
, por lo que nuestro operator !=
global operator !=
Debería entrar en juego y negar el resultado de la sobrecarga del operator ==
que toma dos const test3&
. Por lo tanto, la línea #2
debe imprimir:
!(==)0 // operator == returns true, and is_not_equal() negates it
Finalmente, la tercera prueba involucra dos objetos de valor r del tipo test4
, para los cuales se define operator !=
(Porque los argumentos son convertibles a test4 const&
). Por lo tanto, la línea #3
debe imprimir:
(!=)1
Y aquí hay un ejemplo en vivo que muestra que la salida producida es la esperada.
Así que quiero escribir un automático !=
:
template<typename U, typename T>
bool operator!=(U&& u, T&& t) {
return !( std::forward<U>(u) == std::forward<T>(t) );
}
pero eso es descortés 1 . Asi escribo
// 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 es una clase de rasgos de tipo que dice "es t == u
código válido que devuelve un tipo convertible a bool
".
Así que mejoro mi !=
:
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) );
}
y ahora solo es una anulación válida si existe ==
. Lamentablemente, es un poco codicioso:
struct test {
};
bool operator==(const test&, const test&);
bool operator!=(const test&, const test&);
¡ya que enredará casi todas las test() != test()
lugar de las anteriores !=
Creo que esto no es lo deseado; preferiría llamar explícito !=
reenviar automáticamente a ==
y negar.
Entonces, escribo esta clase de rasgos:
template<typename T, typename U,typename=void>
struct can_not_equal // ... basically the same as can_equal, omitted
que comprueba si T != U
es válido.
Entonces aumentamos el !=
siguiente manera:
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) );
}
el cual, si lo analiza, dice "esta oración es falsa" - operator!=
existe entre T
y U
si es un operator!=
no existe entre T
y U
No es sorprendente que todos los compiladores que he probado segfaults cuando alimenté esto. (cala 3.2, gcc 4.8 4.7.2 intel 13.0.1). Sospecho que lo que estoy haciendo es ilegal, pero me encantaría ver la referencia estándar. (edición: lo que estoy haciendo es ilegal, porque induce una expansión de plantilla recursiva ilimitada, ya que determinar si mi !=
aplica requiere que verifiquemos si mi !=
aplica. La versión enlazada en los comentarios, con #if 1
, da un error sensible).
Pero mi pregunta: ¿hay alguna manera de convencer a mi anulación basada en SFINAE para que ignore "sí mismo" cuando decida si debería fallar o no, o de alguna manera deshacerme del problema auto-referencial de alguna manera? O bien, ¡reduzca la precedencia de mi operator!=
suficientemente bajo como para que cualquier explícito !=
Salga victorioso, incluso si de otro modo no es una buena coincidencia?
El que no comprueba " !=
No existe" funciona razonablemente bien, pero no lo suficiente como para que yo sea tan descortés como para inyectarlo en el espacio de nombres global.
El objetivo es que cualquier código que se compile sin mi "magia" !=
Haga exactamente lo mismo una vez que se introduzca mi "magia" !=
. Si y solo si !=
Por lo demás no es válido y bool r = !(a==b)
está bien formado, ¿debería mi "magia" !=
Patear.
Nota al pie 1 : Si crea una template<typename U, typename T> bool operator!=(U&& u, T&& t)
, SFINAE pensará que cada par de tipos tiene un valor válido !=
Entre ellos. Luego, cuando intenta llamar realmente !=
, Se crea una instancia y no se compila. Además de eso, pisa las funciones de bool operator!=( const foo&, const foo& )
, porque es una mejor foo() != foo()
para foo() != foo()
y foo a, b; a != b;
foo a, b; a != b;
. Considero hacer ambos de estos maleducados.