c++ - una - teoria de russell
La paradoja de Russell en las plantillas de C++ (1)
Esta pregunta ya tiene una respuesta aquí:
Considere este programa:
#include <iostream>
#include <type_traits>
using namespace std;
struct russell {
template <typename barber,
typename = typename enable_if<!is_convertible<barber, russell>::value>::type>
russell(barber) {}
};
russell verify1() { return 42L; }
russell verify2() { return 42; }
int main ()
{
verify1();
verify2();
cout << is_convertible<long, russell>::value;
cout << is_convertible<int, russell>::value;
return 0;
}
Si algún tipo de barber
no es convertible a russell
. intentamos crear una paradoja convirtiéndola en convertible (habilitando un constructor de conversión).
La salida es 00
con tres compiladores populares, aunque los constructores evidentemente están trabajando.
Sospecho que el comportamiento debería ser indefinido, pero no puedo encontrar nada en el estándar.
¿Cuál debería ser la salida de este programa y por qué?
Durante la resolución de sobrecarga, la deducción de los argumentos de la plantilla debe crear una instancia del argumento predeterminado para obtener un conjunto completo de argumentos de la plantilla para crear una instancia de la plantilla de función con (si es posible). Por lo tanto is_convertible<int, russell>
es necesaria la is_convertible<int, russell>
de instancias de is_convertible<int, russell>
, que invoca internamente la resolución de sobrecarga. La plantilla del constructor en russell
está dentro del alcance en el contexto de creación de instancias del argumento de la plantilla predeterminada.
El problema es que is_convertible<int, russell>::value
evalúa el argumento de la plantilla predeterminada de russell
, que a su vez denomina is_convertible<int, russell>::value
.
is_convertible<int, russell>::value
|
v
russell:russell(barber)
|
v
is_convertible<int, russell>::value (not in scope)
La resolución (no adaptada) del problema central 287 parece ser la morada de la regla de facto por los compiladores principales. Debido a que el punto de instanciación viene justo antes de una entidad, la declaración del value
no está dentro del alcance mientras estamos evaluando su inicializador; por lo tanto, nuestro constructor tiene una falla de sustitución y is_convertible
en los main
rendimientos. El número 287 aclara qué declaraciones están dentro del alcance y cuáles no, a saber, el value
.
Clang y GCC difieren ligeramente en cómo tratan esta situación. Tome este ejemplo con una implementación personalizada y transparente del rasgo:
#include <type_traits>
template <typename T, typename U>
struct is_convertible
{
static void g(U);
template <typename From>
static decltype(g(std::declval<From>()), std::true_type{}) f(int);
template <typename>
static std::false_type f(...);
static const bool value = decltype(f<T>()){};
};
struct russell
{
template <typename barber,
typename = std::enable_if_t<!is_convertible<barber, russell>::value>>
russell(barber) {}
};
russell foo() { return 42; }
int main() {}
Clang traduce esto en silencio. GCC se queja de una cadena de recursión infinita: parece argumentar que el value
está ciertamente en el alcance en la instanciación recursiva del argumento predeterminado, y así procede a instanciar el inicializador de value
una y otra vez. Sin embargo, podría decirse que Clang está en la derecha, ya que tanto la frase relevante actual como la redactada en [temp.point]/4 ordenan que el PoI esté antes de la declaración adjunta más cercana. Es decir, esa misma declaración no se considera parte de la instanciación parcial (todavía). Un poco tiene sentido si consideras el escenario anterior. Solución para GCC: emplee un formulario de declaración en el que el nombre no se declara hasta después de que se haya creado una instancia del inicializador.
enum {value = decltype(f<T>()){}};
Esto compila con GCC también .