c++ - programacion - que es un atributo en poo
La plantilla de función de amigo con deducción automática del tipo de retorno no puede acceder a un miembro privado (2)
Lo siento por lo complicado que es el título de esta pregunta; Intenté describir el SSCCE mínimo que construí para este problema.
Tengo el siguiente código:
#include <iostream>
namespace fizz
{
template<typename... Ts>
class bar
{
public:
template<int I, typename... Us>
friend auto foo(const bar<Us...> &);
private:
int i = 123;
};
template<int I, typename... Ts>
auto foo(const bar<Ts...> & b)
{
return b.i;
}
}
int main()
{
std::cout << fizz::foo<1>(fizz::bar<int, float>{});
}
Este código se compila con GCC 5.2 y no con Clang 3.7 :
main.cpp:19:18: error: ''i'' is a private member of ''fizz::bar<int, float>''
return b.i;
^
main.cpp:25:24: note: in instantiation of function template specialization ''fizz::foo<1, int, float>'' requested here
std::cout << fizz::foo<1>(fizz::bar<int, float>{});
^
main.cpp:13:13: note: declared private here
int i = 123;
^
Sin embargo, si cambia el código ligeramente (aunque de una manera que no es exactamente útil para mí, ya que en el código real esto introduciría toneladas de repetitivo):
#include <iostream>
namespace fizz
{
template<typename... Ts>
class bar
{
public:
template<int I, typename... Us>
friend int foo(const bar<Us...> &);
private:
int i = 123;
};
template<int I, typename... Ts>
int foo(const bar<Ts...> & b)
{
return b.i;
}
}
int main()
{
std::cout << fizz::foo<1>(fizz::bar<int, float>{});
}
De repente funciona con ese Clang 3.7 .
La diferencia es que en la versión del código que no se compila con Clang, la plantilla de la función amiga usa la deducción del tipo de retorno auto
C ++ 14, mientras que la versión de trabajo dice claramente que devuelve int
. El mismo problema también ocurre con otras variantes de auto
deducción del tipo de devolución auto
, como auto &&
o const auto &
.
¿Qué compilador tiene razón? Por favor, proporcione algunas citas estándar para respaldar la respuesta, ya que es muy posible que se deba archivar un error para uno (... ojalá no ambos) compiladores ... o un defecto estándar, si ambos son correctos (lo que no sería correcto) t ser la primera vez).
Creo que es un bicho. Quiero abordarlo desde esta dirección. ¿Qué arrugas agrega el tipo de marcador de posición auto
, en comparación con tener un tipo de retorno específico? Desde [dcl.spec.auto]:
El tipo de marcador de posición puede aparecer con un declarador de función en decl-specifier-seq , type-specifier-seq , conversion-function-id , o trailing-return-type , en cualquier contexto en el que dicho declarador sea válido. Si el declarador de función incluye un tipo de retorno-retorno (8.3.5), ese tipo-retorno-retorno especifica el tipo de retorno declarado de la función. De lo contrario, el declarador de funciones declarará una función. 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 las declaraciones de retorno en el cuerpo de la función, si corresponde.
auto
puede aparecer en la declaración y definición de foo
, y es válido.
Si el tipo de una entidad con un tipo de marcador de posición no definido es necesario para determinar el tipo de una expresión, el programa está mal formado. Sin embargo, una vez que se ha visto una declaración de
return
en una función, el tipo de devolución deducido de esa declaración se puede utilizar en el resto de la función, incluso en otras declaraciones de devolución. [Ejemplo:
auto n = n; // error, n’s type is unknown auto f(); void g() { &f; } // error, f’s return type is unknown auto sum(int i) { if (i == 1) return i; // sum’s return type is int else return sum(i-1)+i; // OK, sum’s return type has been deduced }
—En ejemplo]
La primera vez que debamos usar determinar el tipo de una expresión, el tipo de retorno de la función ya habrá sido deducido del return
en la definición de foo()
, por lo que esto sigue siendo válido.
Las redeclaraciones o especializaciones de una función o plantilla de función con un tipo de retorno declarado que usa un tipo de marcador de posición también usarán ese marcador de posición, no un tipo deducido.
Estamos usando auto
en ambos lugares, por lo que tampoco violamos esta regla.
En resumen, hay varias cosas que diferencian un tipo de retorno específico de un tipo de retorno de marcador de posición de una declaración de función. Pero todos los usos de auto
en el ejemplo son correctos, por lo que foo
espacio de nombres debe verse como una redeclaración y definición del friend auto foo
primer friend auto foo
declarado dentro de la bar
plantillas de clase. El hecho de que clang acepte lo primero como una redeclaración para el tipo de devolución int
pero no para auto
, y no hay ninguna diferencia relevante para auto
, definitivamente sugiere que esto es un error.
Además, si elimina el parámetro int I
template para que pueda llamar a foo
sin calificar, clang informará la llamada como ambigua:
std::cout << foo(fizz::bar<int, float>{});
main.cpp:26:18: error: call to ''foo'' is ambiguous
std::cout << foo(fizz::bar<int, float>{});
^~~
main.cpp:10:21: note: candidate function [with Us = <int, float>]
friend auto foo(const bar<Us...> &);
^
main.cpp:17:10: note: candidate function [with Ts = <int, float>]
auto foo(const bar<Ts...>& b)
^
Entonces, tenemos dos plantillas de funciones llamadas foo
en el mismo espacio de nombres (ya que de [namespace.memdef] la declaración de friend
para foo
lo colocará en el espacio de nombres más cercano) que toman los mismos argumentos y tienen el mismo tipo de retorno ( auto
). Eso no debería ser posible.
Parece que tu primer ejemplo debería funcionar. Hay una declaración en C ++ 14 (7.1.6.4 p12):
Las redeclaraciones o especializaciones de una función o plantilla de función con un tipo de retorno declarado que usa un tipo de marcador de posición también usarán ese marcador de posición, no un tipo deducido. [Ejemplo:
. . .
template <typename T> struct A {
friend T frf(T);
};
auto frf(int i) { return i; } // not a friend of A<int>
El motivo para el ejemplo parece ser que para hacer coincidir las declaraciones (y hacer que la función definida sea un amigo), la declaración de frf en la estructura A también debería usar auto
. Esto me implica que está permitido tener una declaración de amigo con un tipo de retorno automático y luego definir la función de amigo (y también usar el auto) No puedo encontrar nada que haga que esto funcione de manera diferente para una plantilla de función miembro, como en su ejemplo.