Deducción de tipo de devolución para funciones de amigo en clase

Aquí hay un pequeño experimento con deducción tipo devolución para funciones de amigos en clase (usando Clang 3.4 SVN y g ++ 4.8.1 con std=c++1y en ambos casos) que no está documentado en el papel de trabajo vinculado

#include <iostream>

struct A
{
    int a_;
    friend auto operator==(A const& L, A const& R) 
    { 
        return L.a_ == R.a_; // a_ is of type int, so should return bool
    }
};

template<class T>
struct B
{
    int b_;
    friend auto operator==(B const& L, B const& R) 
    { 
        return L.b_ == R.b_; // b_ is of type int, so should return bool
    }
};

using BI = B<int>;

int main()
{
    std::cout << (A{1} == A{2}) << "\n";    // OK for Clang, ERROR for g++
    std::cout << (BI{1} == BI{2}) << "\n";  // ERROR for both Clang and g++
}

Ejemplo en vivo.

Pregunta: ¿Se admite la deducción automática del tipo de devolución para las funciones de amigos en clase en C++ 14?

preguntado el 21 de septiembre de 13 a las 12:09

No creo que el problema sea específicamente sobre funciones de amigo definido dentro de las plantillas. Ejemplo vivo Hay una "deducción de tipo de devolución para una plantilla de función con un marcador de posición en su tipo declarado cuando se crea una instancia de la definición, incluso si el cuerpo de la función contiene una declaración de devolución con un operando que no depende del tipo", pero no puedo encontrar nada sobre los miembros de plantillas de clase o funciones amigas definidas dentro de plantillas de clase. -

Como hay [temp.friend]/4 "Cuando una función se define en una declaración de función amiga en una plantilla de clase, la función se instancia cuando se usa odr", tal vez la redacción en [dcl.spec.auto] /12 debe ser "Deducción de tipo de devolución para una plantilla de función, función miembro de una plantilla de clase y función amiga definida dentro de una plantilla de clase". -

@DyP tnx por estas citas. El documento de trabajo al que vinculé anteriormente aún no se refleja en el borrador actual del Estándar, pero encuentro que el documento es bastante complicado de leer y no hay ningún ejemplo que se asemeje al código anterior. -

Su ejemplo es engañoso, los errores están donde se definen las funciones, no donde intenta llamarlas. Puede comentar ambas líneas en main() y todavía falla (clang trunk incluso falla en operator==(const B&, const B&).) -

1 Respuestas

Con respecto a las otras respuestas: estamos tratando explícitamente con n3638 aquí, y cómo se incorpora en los borradores recientes de C++1y.

Estoy usando 9514cc28 del repositorio github del comité, que ya incorpora algunas correcciones/cambios (menores) en n3638.

n3638 permite explícitamente:

struct A {
  auto f(); // forward declaration
};
auto A::f() { return 42; }

Y, como podemos inferir de [dcl.spec.auto], donde se especifica esta función, incluso lo siguiente será legal:

struct A {
  auto f(); // forward declaration
};

A x;

auto A::f() { return 42; }

int main() { x.f(); }

(pero más sobre esto más adelante)

Esto es fundamentalmente diferente de cualquier tipo-retorno-final o búsqueda de nombre dependiente, como auto f(); es una declaración preliminar, similar a struct A;. Debe completarse más adelante, antes de que se use (antes de que se requiera el tipo de devolución).

Además, los problemas en el OP están relacionados con errores internos del compilador. La compilación reciente de clang++3.4 trunk 192325 Debug+Asserts no se compila porque falla una afirmación al analizar la línea return L.b_ == R.b_;. No he comprobado con una versión reciente de g ++ a partir de ahora.


¿El ejemplo del OP es legal para un n3638?

Esto es un poco complicado en mi opinión. (Siempre me refiero a 9514cc28 en esta sección).

1. ¿Dónde está permitido usar `auto`?

[dcl.spec.auto]

6 Un programa que utiliza auto or decltype(auto) en un contexto no permitido explícitamente en esta sección está mal formado.

2 El tipo de marcador de posición puede aparecer con un declarador de función en el decl-specifier-seq, tipo-especificador-seq, ID de función de conversióno tipo-retorno-final, en cualquier contexto en el que dicho declarante sea válido.

/5 también define algunos contextos, pero aquí son irrelevantes.

Por lo tanto, auto func() y auto operator@(..) están generalmente permitidos (esto se deduce de la composición de una declaración de función como T D, Donde T es de la forma decl-specifier-seq y auto es un especificador de tipo).


2. ¿Está permitido escribir `auto func();`, es decir, una declaración que no es una definición?

[dcl.spec.auto]/1 dice

El auto y decltype(auto) especificadores de tipo designar un tipo de marcador de posición que se reemplazará más tarde, ya sea por deducción de un inicializador o por especificación explícita con un tipo-retorno-final.

y 2

Si el tipo de retorno declarado de la función contiene un tipo de marcador de posición, el tipo de retorno de la función se deduce de return instrucciones en el cuerpo de la función, si las hay.

aunque no explícitamente permitir una declaración como auto f(); para una función (es decir, una declaración sin definición), está claro de n3638 y [dcl.spec.auto]/11 que está destinado a ser permitido, y no prohibido explícitamente.


3. ¿Qué pasa con las funciones de amigos?

Hasta ahora, el ejemplo

struct A
{
    int a_;
    friend auto operator==(A const& L, A const& R);
}

auto operator==(A const& L, A const& R)
{ return L.a_ == R.a_; }

debe estar bien formado. La parte interesante ahora es la definición de la función amigo dentro de la definición de A, y

struct A
{
    int a_;
    friend auto operator==(A const& L, A const& R)
    { return L.a_ == R.a_; } // allowed?
}

En mi opinión, está permitido. Para respaldar esto, citaré la búsqueda de nombres. La búsqueda de nombres dentro de la definición de funciones definidas en una declaración de función amiga sigue a la búsqueda de nombres de funciones miembro según [basic.lookup.unqual]/9. /8 de la misma sección especifica la búsqueda no calificada de nombres usados ​​dentro de cuerpos de funciones miembro. Una de las formas en que se puede declarar el uso de un nombre es que "será un miembro de la clase X o ser miembro de una clase base de X (10.2)". Esto permite que el ampliamente conocido

struct X
{
    void foo() { m = 42; }
    int m;
};

Note como m no se declara antes de su uso en foo, pero es miembro de X.

De esto, concluyo que incluso

struct X
{
    auto foo() { return m; }
    int m;
}

esta permitido. Esto es compatible con clang ++ 3.4 troncal 192325. La búsqueda de nombres requiere interpretar esta función solo después de la struct se ha completado, considere también:

struct X
{
    auto foo() { return X(); }
    X() = delete;
};

De manera similar, el cuerpo de funciones amigas definidas dentro de una clase solo se puede interpretar una vez que la clase está completa.


4. ¿Qué pasa con las plantillas?

Específicamente, ¿qué pasa con friend auto some_function(B const& L) { return L.b_; }?

En primer lugar, la nombre-clase-inyectado B es equivalente a B<T>, consulte [temp.local]/1. Se refiere a la instanciación actual ([tipo de dependencia temporal]/1).

El expresión-id L.b_ se refiere a un miembro de la instanciación actual (/4). también es un miembro dependiente de la instanciación actual -- esta es una adición hecha después de C++11, ver DR1471, y no sé qué pensar al respecto: [temp.dep.expr]/5 dice esto expresión-id is no depende del tipo y, por lo que veo, [temp.dep.constexpr] no dice que dependa del valor.

Si el nombre en L.b_ no era dependiente, la búsqueda de nombres seguiría las reglas de "búsqueda de nombres habituales" según [temp.nondep]. De lo contrario, será divertido (la búsqueda de nombres de dependientes no está muy bien especificada), pero teniendo en cuenta que

template<class T>
struct A
{
    int foo() { return m; }
    int m;
};

también es aceptado por la mayoría de los compiladores, creo que la versión con auto debe ser válido, también.

También hay una sección sobre amigos de plantillas en [temp.friend], pero en mi opinión no arroja luz sobre la búsqueda de nombres aquí.


Ver también esta discusión tan relevante en el foro isocpp.

Respondido 11 Oct 13, 22:10

+10 si pudiera, pero cosas geniales que necesito procesar. ¡Muchas gracias! - PlantillaRex

IIRC, las funciones amigas definidas dentro de las plantillas de clase son funciones regulares en el espacio de nombres adjunto y solo se pueden encontrar a través de ADL. Dado que menciona algunos tecnicismos relacionados con la creación de instancias, me pregunto si hay interferencia de la búsqueda de nombres de 2 fases aquí. Concretamente, exactamente cuando ¿Se deduce el tipo de devolución? - PlantillaRex

@TemplateRex [clase.amigo]/7 "A friend función definida en una clase está en el ámbito (léxico) de la clase en la que está definida". - esto afecta la búsqueda no calificada (además, es implícitamente inline). "Específicamente, ¿exactamente cuándo se deduce el tipo de retorno?" Buena pregunta; Ya dije en los comentarios a su pregunta que no puedo encontrar una declaración explícita para esto. Sin embargo, como también aparece en la discusión isocpp vinculada, no puede ser antes de que se complete la clase adjunta. - Dyp

Bien, supongo que hay que tener en cuenta tanto el QoI estándar como el actual, tal vez se aclare en los próximos meses. Aceptaré la respuesta al final del fin de semana en el caso remoto de que surja algo mejor. - PlantillaRex

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.