¿Cómo se pueden usar tipos no especificados en expresiones 'requiere' de C ++ 20?

Aug 16 2020

Estoy tratando de escribir un concepto de C ++ 20 para expresar el requisito de que un tipo tenga un método determinado, que toma un argumento, pero para los propósitos de este concepto no me importa cuál sea el tipo de argumento.

Intenté escribir algo como:

template <typename T>
concept HasFooMethod = requires(T t, auto x)
{
    { t.Foo(x) } -> std::same_as<void>;
};

sin embargo, tanto gcc como clang rechazan esto, dando un error de que 'auto' no se puede usar en la lista de parámetros de una expresión require de esta manera.

Una alternativa sería poner el tipo de 'x' como un segundo parámetro de plantilla:

template <typename T, typename TX>
concept HasFooMethod = requires(T t, TX x)
{
    { t.Foo(x) } -> std::same_as<void>;
};

pero luego esto requiere que TX se especifique explícitamente siempre que se use el concepto, no se puede deducir:

struct S { void Foo(int); };
static_assert(HasFooMethod<S>);         // doesn't compile
static_assert(HasFooMethod<S, int>);    // the 'int' must be specified

¿Hay alguna forma de escribir un concepto que le permita a Foo tomar un argumento de tipo no especificado ?

La pregunta Definición de concepto que requiere una función de miembro de plantilla restringida es muy similar, pero no la misma: esa pregunta pregunta cómo requerir que un método (con plantilla) pueda tomar cualquier tipo que satisfaga un concepto dado, mientras que esta pregunta trata de requerir que un método tome algún tipo en particular, aunque ese tipo no está especificado. En términos de cuantificadores, la otra pregunta se refiere a la cuantificación universal (limitada), mientras que esta se refiere a la cuantificación existencial. La respuesta de la otra pregunta tampoco se aplica a mi caso.

Respuestas

4 NicolBolas Aug 17 2020 at 13:48

Los conceptos no están destinados a proporcionar el tipo de funcionalidad que busca. Entonces no lo proporcionan.

Un concepto está destinado a restringir las plantillas, para especificar un conjunto de expresiones o declaraciones que una plantilla tiene la intención de usar (o al menos ser libre de usar) en su definición.

Dentro de la plantilla que es tan restrictiva, si escribe la expresión t.Foo(x), entonces conoce el tipo de x. Es un tipo concreto, un parámetro de plantilla o un nombre derivado de un parámetro de plantilla. De cualquier manera, el tipo de xestá disponible en la plantilla que se restringe.

Entonces, si desea restringir dicha plantilla, use tanto el tipo de tcomo el tipo de x. Ambos están disponibles para usted en ese momento, por lo que no hay ningún problema en crear tal restricción. Es decir, la restricción no está activada Tcomo un tipo aislado; está en la asociación entre Ty X.

Los conceptos no están destinados a funcionar en el vacío, sin ninguna asociación con el lugar real de uso de la restricción. No debe concentrarse en crear conceptos unarios para que los usuarios puedan static_assertsus clases en contra de ellos. Los conceptos no están pensados ​​para probar si un tipo los cumple (que es básicamente lo que estás static_asserthaciendo); están destinados a restringir la definición de plantilla que los usa .

Tu restricción debe ser FooCallableWith, no HasFooMethod.

NathanReed Aug 17 2020 at 16:30

Algo parecido a esto se puede lograr mediante la definición de un tipo de adaptador que se puede convertir implícitamente en (casi) cualquier cosa:

struct anything
{
    // having both these conversions allows Foo's argument to be either
    // a value, an lvalue reference, or an rvalue reference

    template <typename T>
    operator T&();

    template <typename T>
    operator T&&();
};

Tenga en cuenta que no es necesario implementar estos operadores, ya que solo se usarán en un contexto no evaluado (y, de hecho, no se podrían implementar para todos los tipos T).

Entonces, HasFooMethodse puede escribir como:

template <typename T>
concept HasFooMethod = requires(T t, anything a)
{
    { t.Foo(a) } -> std::same_as<void>;
};