Pregunta ¿Por qué se permiten las lambdas genéricas mientras que las estructuras anidadas con métodos de plantillas no lo son?


Por lo que yo entiendo, las lambdas genéricas se transforman en objetos de estructuras de alcance local con plantillas operator(). Esto hace que la lambda genérica sea una herramienta muy poderosa y fácil de usar. Por otro lado, uno puede crear estructuras anidadas en la función, cuando la estructura tiene un miembro con plantilla, por ejemplo:

#include <iostream>

int main() {
    struct inner {
    template <class T>
       void operator()(T &&i) { }
    };
    return 0;
}

o está modelado por sí mismo:

int main() {
    template <class T>
    struct inner {
       void operator()(T &&i) { }
    };
    return 0;
}

el compilador parece tener un problema al compilarlo:

error: invalid declaration of member template in local class

y

error: a template declaration cannot appear at block scope

Supongo que el problema radica más en el estándar c ++ que en el error del compilador. ¿Cuáles son las razones por las que a los lambdas se les permite tener miembros con plantillas y no a las estructuras locales?

encontré esta pregunta, pero creo que la respuesta está desactualizada (no creo que sea cierto incluso para c ++ 11).


32
2017-10-25 14:19


origen


Respuestas:


Esto es cuestión central 728, que se archivó antes de que las lambdas genéricas fueran una cosa.

Mencionaste lambdas genéricos y que eran idénticos a las clases locales con el miembro correspondiente template operator(). Sin embargo, en realidad no lo son, y las diferencias están relacionadas con las características de implementación. Considerar

template <typename T>
class X {
    template <typename>
    void foo() {
        T t;
    }
};

Y

template <typename T>
auto bar() {
    return [] (auto) {T t;};
};

Crear una instancia de estas plantillas con <void> estará bien en el primer caso, pero mal formado en el segundo. ¿Por qué está bien en el primer caso? foo no necesita ser instanciable para cada particular T, pero solo uno de ellos (esto sería [temp.res] / (8.1))

¿Por qué mal formado en el segundo caso? El cuerpo genérico de lambda se crea una instancia, parcialmente, utilizando los argumentos de plantilla proporcionados. Y la razón para esta instanciación parcial es el hecho de que ...

... los ámbitos léxicos utilizados al procesar una definición de función son fundamentalmente transitorios, lo que significa que retrasar la instanciación de una parte de la definición de una plantilla de función es difícil de soportar.

(Richard Smith) Debemos crear una instancia suficiente de la "plantilla" local para que sea independiente del contexto local (que incluye los parámetros de plantilla de la plantilla de función adjunta).

Esto también está relacionado con la justificación de [expr.prim.lambda] / 13, que ordena que una entidad sea capturada implícitamente por una lambda si ...

nombra la entidad en una expresión potencialmente evaluada ([basic.def.odr]) donde el adjunto expresión completa depende de un parámetro lambda genérico declarado dentro del alcance de alcance del lambda-expresión.

Es decir, si tengo un lambda como [=] (auto x) {return (typename decltype(x)::type)a;}, dónde a es alguna variable de alcance de bloque de una función adjunta, independientemente de si x's type typedef es para void o no, el elenco causará una captura de a, porque debemos decidir sobre esto sin esperar una invocación de la lambda. Para una discusión de este problema, vea el propuesta original sobre lambdas genéricos.

La conclusión es que posponer completamente la instanciación de una plantilla de miembro no es compatible con el modelo utilizado por (al menos una) implementación (es) principal (es), y dado que esa es la semántica esperada, la característica no se introdujo.


¿Esa era la motivación original para esta restricción? Se introdujo en algún momento entre enero y mayo de 1994, sin ningún documento que lo cubriera, por lo que solo podemos tener una idea aproximada de las nociones predominantes de este papelLa justificación de por qué las clases locales no deben ser argumentos de plantilla:

Las plantillas de clase y las clases generadas a partir de la plantilla son alcance global   entidades y no pueden referirse a entidades de alcance local.

Quizás en aquel entonces, uno quería BESAR.


27
2017-10-25 15:28



Supongo que el problema radica más en el estándar c ++

Correcto. Esto está estipulado en [temp] para plantillas de clases:

UN plantilla-declaración puede aparecer solo como un ámbito de espacio de nombres o una declaración de alcance de clase.

y [temp.mem] para plantillas de miembros:

Una clase local de tipo sin cierre no debe tener plantillas de miembro.


¿Cuáles son las razones por las que a los lambdas se les permite tener miembros con plantillas y no a las estructuras locales?

Porque una vez que tuvimos lambdas en C ++ 11, se consideró que sería extremadamente útil ampliar ese concepto para tener lambdas genéricas. Hubo un propuesta para dicha extensión de idioma, que era revisado  y revisado y adoptado

Por otro lado, todavía no se ha presentado una propuesta (hasta donde tengo conocimiento de una breve búsqueda) que establece la motivación para la necesidad de plantillas de miembros en clases locales que no se resuelven adecuadamente mediante una lambda genérica. .

Si siente que se trata de un problema importante que debe resolverse, siéntase libre de enviar una propuesta después de diseñar una motivación reflexiva de por qué las plantillas de miembros locales son importantes.


3
2017-10-25 15:23