Según el Explorador de compiladores de Godbolt (consulte demostración), el siguiente código se compila con GCC (tanto 10.2 como troncal) y genera 3628800, pero no se puede compilar con Clang (tanto 11.0.1 como trunk). En ambos casos, se utiliza -std=c++17. También compila con MSVC 19, pero no con otros compiladores. ¿Por qué es así y de quién es el comportamiento correcto?

#include <iostream>

int main()
{
   std::cout << [](auto f) { return f(f); }(
        [](auto& h) {
            return [&h](auto n) {
                if (n < 2)
                    return 1;
                else
                    return n * h(h)(n - 1);
                };
            })(10) << std::endl;
}


Además, incluso GCC y MSVC rechazan el código si reemplazo auto n con int n o si reemplazo if-else con el operador ternario (return n < 2 ? 1 : (n * h(h)(n - 1));).

6
zabolekar 13 mar. 2021 a las 21:35

1 respuesta

La mejor respuesta

Este programa está mal formado, no se requiere diagnóstico , por lo que ambas implementaciones (de hecho, cualquier implementación) son correctas. La regla violada es [temp.res.general] /6.4:

una instanciación hipotética de una plantilla inmediatamente después de su definición estaría mal formada debido a una construcción que no depende de un parámetro de plantilla

La plantilla aquí es la operator() más interna: usa la especialización del operator() que contiene inmediatamente (con el tipo de cierre del que es miembro como un argumento de plantilla), que tiene un tipo de retorno deducido. "Inmediatamente siguiente" aquí está (todavía) dentro de la declaración de retorno de la que se deducirá ese tipo, por lo que la instanciación estaría mal formada debido a solo h(h), que no implica un parámetro de plantilla de esa plantilla ( es decir , el tipo de n).

GCC y MSVC no se molestan en hacer la verificación semántica del tipo de retorno deducido hasta la instanciación final (con int para el 10), momento en el que se conoce el tipo de retorno: es solo la variante apropiada de la lambda más interna . Sin embargo, si esa lambda más interna no es genérica, la verificación se lleva a cabo durante la instanciación del operator() contenedor y se produce el mismo error.

Esta diferencia de comportamiento se puede ver en un ejemplo mucho más simple:

auto g();
template<class T> auto f(T x) {return g()-x;}

Clang ya ha rechazado en este punto, pero GCC acepta, quizás seguido de

auto g() {return 1;}
int main() {return f(1);}
4
Davis Herring 14 mar. 2021 a las 01:59