c++ - Conversión implícita al sobrecargar operadores para clases de plantillas
templates operator-overloading (3)
Me gustaría saber por qué la conversión de tipos implícita no funciona con la sobrecarga de operadores externos en las plantillas de clase. Aquí está la versión de trabajo, sin plantilla:
class foo
{
public:
foo() = default;
foo(int that)
{}
foo& operator +=(foo rhs)
{
return *this;
}
};
foo operator +(foo lhs, foo rhs)
{
lhs += rhs;
return lhs;
}
Como era de esperar, las siguientes líneas se compilan correctamente:
foo f, g;
f = f + g; // OK
f += 5; // OK
f = f + 5; // OK
f = 5 + f; // OK
Por otro lado, cuando la clase foo
se declara como una plantilla simple como esta:
template< typename T >
class foo
{
public:
foo() = default;
foo(int that)
{}
foo& operator +=(foo rhs)
{
return *this;
}
};
template< typename T >
foo< T > operator +(foo< T > lhs, foo< T > rhs)
{
lhs += rhs;
return lhs;
}
Las siguientes líneas se compilan con errores:
foo< int > f, g;
f = f + g; // OK
f += 5; // OK
f = f + 5; // Error (no match for operator+)
f = 5 + f; // Error (no match for operator+)
Me gustaría entender por qué el compilador (GCC 4.6.2) no puede realizar la conversión de tipos implícita utilizando el constructor de conversión para la versión de plantilla de la clase. ¿Es ese el comportamiento esperado? Aparte de crear manualmente todas las sobrecargas necesarias, ¿hay alguna solución para esto?
La razón por la que no solo funciona es que las conversiones de tipo implícitas (es decir, a través de constructores) no se aplican durante la deducción de argumentos de la plantilla. Pero funciona si haces que el operador externo sea un amigo, ya que el tipo T es conocido, lo que permite al compilador investigar qué se puede convertir para que los argumentos coincidan.
Hice un ejemplo basado en el tuyo (pero eliminé el material de C ++ 11), inspirado en el Item 46 (una clase de número racional) en Scott Meyers Effective C ++ (ed 3). Tu pregunta es casi una coincidencia exacta con ese elemento. Scott también señala que ... "este uso de un amigo no está relacionado con el acceso de partes no públicas de la clase".
Esto también permitirá trabajar con mezclas de foo <T>, foo <U> etc. siempre que se puedan agregar T y U, etc.
También mira este post: ambigüedad de la sobrecarga de la adición de C ++
#include <iostream>
using namespace std;
template< class T >
class foo
{
private:
T _value;
public:
foo() : _value() {}
template <class U>
foo(const foo<U>& that) : _value(that.getval()) {}
// I''m sure this it can be done without this being public also;
T getval() const { return _value ; };
foo(const T& that) : _value(that) {}
friend const foo operator +(foo &lhs,const foo &rhs)
{
foo result(lhs._value+rhs._value);
return result;
};
friend const foo operator +(foo &lhs,const T &rhsval)
{
foo result(lhs._value+rhsval);
return result;
};
friend const foo operator +(const T &lhsval,foo &rhs)
{
foo result(lhsval+rhs._value);
return result;
};
friend foo& operator +=(foo &lhs,const foo &rhs)
{
lhs._value+=rhs._value;
return lhs;
};
friend std::ostream& operator<<(std::ostream& out, const foo& me){
return out <<me._value;
}
};
int main(){
foo< int > f, g;
foo< double > dd;
cout <<f<<endl;
f = f + g;
cout <<f<<endl;
f += 3 ;
cout <<f<<endl;
f = f + 5;
cout <<f<<endl;
f = 7 + f;
cout <<f<<endl;
dd=dd+f;
cout <<dd<<endl;
dd=f+dd;
cout <<dd<<endl;
dd=dd+7.3;
cout <<dd<<endl;
}
Le hice esta pregunta a los autores de la biblioteca en MS y recibí una respuesta extremadamente informativa de Stephan Lavavej, así que le doy el crédito completo por esta información.
El error de compilación que obtiene en el caso de la plantilla se debe al hecho de que la deducción del argumento de la plantilla se ejecuta antes de la resolución de sobrecarga, y la deducción del argumento de la plantilla necesita coincidencias exactas para agregar algo al conjunto de sobrecarga.
En detalle, la deducción de argumentos de la plantilla examina cada par de parámetros tipo P y tipo de argumento A, e intenta encontrar sustituciones de plantilla que harán que A coincida exactamente con P. Después de encontrar coincidencias para cada argumento, verifica la coherencia (de modo que si llama bar(foo<T>, foo<T>)
con T = int para el primer parámetro y T = doble como el segundo, también falla). Solo después de que se hayan sustituido con éxito las coincidencias exactas en la firma de la función, se agregará esa firma al conjunto de funciones candidatas para la resolución de sobrecargas.
Solo después de que todas las funciones ordinarias (encontradas a través de la búsqueda de nombres) y las firmas de la plantilla de función coincidente se hayan agregado al conjunto de sobrecarga, se ejecutará la resolución de sobrecarga, en cuyo punto todas estas firmas de funciones se evaluarán para una "mejor coincidencia", durante la cual las conversiones implícitas será considerado.
Para el caso del operator+(foo<T>, foo<T>)
con foo<int> + 5
, la deducción del argumento de la plantilla no puede encontrar una sustitución para T que haga que la expresión foo<T>
coincida exactamente con int
, por lo que la sobrecarga del operador + se desecha como candidato y la conversión implícita nunca se ve.
La opinión aquí parece ser que esto generalmente es algo bueno, ya que hace que las plantillas sean mucho más predecibles, dejando el reino de comportamientos implícitos extraños para sobrecargar la resolución.
El estándar tiene mucho que decir sobre esto en:
14.8.2.1 Deducir argumentos de plantilla de una llamada de función
"La deducción de los argumentos de la plantilla se realiza comparando cada tipo de parámetro de la plantilla de función (llámelo P) con el tipo del argumento correspondiente de la llamada (llámelo A) como se describe a continuación. ...
... En general, el proceso de deducción intenta encontrar valores de argumento de plantilla que harán que la deducción A sea idéntica a A (después de que el tipo A se transforme como se describe anteriormente) "
A continuación, se enumeran algunos casos especiales en los que esta regla tiene excepciones relacionadas con calificadores de cv (por lo que T & será compatible con const T &), y coincidencia de clases derivadas (en algunos casos puede coincidir Derived & con Base &) pero básicamente, coincidencia exacta es la regla
Todas las foo<T>
posibles son conversiones igualmente válidas desde int
porque el constructor toma int
, no el tipo de plantilla. El compilador no puede usar el otro parámetro en el operador para adivinar a cuál te refieres, por lo que obtienes el error. Si le dices explícitamente qué instancia quieres, creo que funcionaría.