¿Es la modificación de un concepto una mejora o una mala práctica?

Aug 20 2020

Parece que puede poner lambda en el concepto y luego escribir código en él. Tomemos esto como ejemplo. Preferiré los conceptos estándar para tales conceptos y tener en cuenta que esto es solo para los propósitos de este ejemplo: godbolt

template<class T>
concept labdified_concept =
    requires {
            [](){                 
                T t, tt; // default constructible
                T ttt{t}; // copy constructible
                tt = t; //copy assignable
                tt = std::move(t); // move assignable
            };
        };

En vez de:

template<class T>
concept normal_concept = 
    std::default_initializable<T> && std::movable<T> && std::copy_constructible<T>;

¿Es la lambdificación una mejora o una mala práctica? También desde el punto de legibilidad.

Respuestas

2 Barry Aug 21 2020 at 01:10

Esto no debería ser válido. El punto de permitir lambdas en contextos no evaluados no era permitir repentinamente SFINAE en declaraciones.

Tenemos algunas palabras en [temp.deduct] / 9 que aclaran esto:

Una expresión lambda que aparece en un tipo de función o un parámetro de plantilla no se considera parte del contexto inmediato a los efectos de la deducción del argumento de plantilla. [ Nota : La intención es evitar requerir implementaciones para lidiar con fallas de sustitución que involucren declaraciones arbitrarias. [ Ejemplo :

template <class T>
  auto f(T) -> decltype([]() { T::invalid; } ());
void f(...);
f(0);               // error: invalid expression not part of the immediate context

template <class T, std::size_t = sizeof([]() { T::invalid; })>
  void g(T);
void g(...);
g(0);               // error: invalid expression not part of the immediate context

template <class T>
  auto h(T) -> decltype([x = T::invalid]() { });
void h(...);
h(0);               // error: invalid expression not part of the immediate context

template <class T>
  auto i(T) -> decltype([]() -> typename T::invalid { });
void i(...);
i(0);               // error: invalid expression not part of the immediate context

template <class T>
  auto j(T t) -> decltype([](auto x) -> decltype(x.invalid) { } (t));   // #1
void j(...);                                                            // #2
j(0);               // deduction fails on #1, calls #2

- ejemplo final ] - nota final ]

Simplemente no tenemos algo equivalente para los requisitos. El comportamiento de gcc es realmente el que esperarías:

template <typename T> concept C = requires { []{ T t; }; };
struct X { X(int); };
static_assert(!C<X>); // ill-formed

Debido a que el cuerpo de la lambda está fuera del contexto inmediato, no es un error de sustitución, es un error grave.

5 NicolBolas Aug 20 2020 at 21:58

Ignorando las obvias fallas de legibilidad en este mecanismo, en realidad no funciona . Considera lo siguiente:

template<labdified_concept T>
void foo(T t) {}

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

Las reglas de los conceptos nos dicen que si un dado Tno satisface labdified_concept, entonces el otro foodebería ser instanciado en su lugar. Pero eso no es lo que sucede si proporcionamos SStal plantilla. En cambio, obtenemos un error grave porque labdified_concept<SS>no se puede crear una instancia.

El contenido de una requiresexpresión tiene un manejo especial que permite que ciertos tipos de errores se consideren fallas para cumplir con el requisito. Pero ese manejo no se aplica al cuerpo de una lambda. Allí, el código mal formado está mal formado y, por lo tanto, obtiene un error de compilación al intentar crear una instancia.

E incluso si funcionó, todavía no funciona. Los conceptos tienen reglas complejas para la subsunción de conceptos, lo que permite considerar diferentes conceptos más altamente especializados que otros. Esto permite sobrecargar diferentes conceptos , lo que permite llamar al concepto más restringido. Por ejemplo, un concepto que solo requiere default_initializablees más genérico que uno que requiere default_initializabley moveable. Así, si un tipo cumple con ambos, se tomará este último porque es más restringido.

Pero esto solo funciona debido a las reglas especiales para concepts. Ocultar requisitos en lambdas no permitiría que esto funcionara.