2012-04-29 10 views
9

El siguiente códigode búsqueda conversión implícita en la plantilla de falla para el amigo función definida fuera de la clase

#include <cassert> 
#include <cstddef> 

template <typename T> 
struct foo { 
    foo(std::nullptr_t) { } 
    //friend bool operator ==(foo lhs, foo rhs) { return true; } 

    template <typename U> 
    friend bool operator ==(foo<U> lhs, foo<U> rhs); 
}; 

template <typename T> 
inline bool operator ==(foo<T> lhs, foo<T> rhs) { return true; } 

int main() { 
    foo<int> p = nullptr; 
    assert(p == nullptr); 
} 

falla al compilar con el mensaje de error

foo.cpp:18:5: error: no match for ' operator== ' in ' p == nullptr '
foo.cpp:18:5: note: candidate is:
foo.cpp:14:13: note: template<class T> bool operator==(foo<T>, foo<T>)
foo.cpp:14:13: note: template argument deduction/substitution failed:
foo.cpp:18:5: note: mismatched types ' foo<T> ' and ' std::nullptr_t '

Sin embargo, si uso la definición dentro de la clase en cambio, el código funciona como se esperaba.

Permítanme decir que entiendo el mensaje de error: la plantilla argumento T no se puede deducir el tipo de nullptr (dicho sea de paso, decltype(*nullptr) no compila). Además, esto puede arreglarse aquí simplemente definiendo la función dentro de la clase.

Sin embargo, por razones de uniformidad (hay otras funciones de amigo que I necesitan para definir afuera) Me gustaría definir esta función fuera de la clase.

¿Existe un "truco" para hacer que una definición fuera de clase funcione?

+1

Creo que quieres decir "función miembro" y "función no miembro" en lugar de "en línea" y "no-inline". En su ejemplo, tiene una función en línea no miembro, y creo que está diciendo que funciona si la convierte en una función miembro. –

+0

@VaughnCato ¿Los amigos cuentan como funciones miembro? No estoy seguro de eso. Ciertamente no están en el espacio de nombres de la clase. –

+0

Las funciones de amigo serán funciones que no sean miembro, pero pueden o no estar en línea, y pueden definirse interna o externamente para la clase. En su caso, tiene una función en línea que se define externamente a la clase. –

Respuesta

3

Abhiji Ya te he dado la solución básica, pero pensé en exponer un poco, ya que es un problema interesante.

Si se declara una función friend dentro de una clase de plantilla, así:

template <typename T> 
struct A { 
    friend void f(A); 
}; 

Entonces lo que está diciendo es que cualquier función no plantilla llamada f que toma una como parámetro será un amigo A. Por lo que tendría que definir estas funciones por separado:

inline void f(A<int>) {...} 
inline void f(A<float>) {...} 
// etc. 

Aunque definirlo dentro de la clase es un atajo.

En este caso, no hay forma de hacer una plantilla que defina el amigo f (A) para cada T, porque ya ha indicado que es la función sin plantilla que es el amigo. Es el hecho mismo de que es una función que no es de plantilla y que lo hace utilizable en su ejemplo, porque las funciones que no son de plantilla permiten más conversiones que las funciones de plantilla cuando el compilador busca funciones coincidentes.

Hay una solución bastante general, aunque es un poco desordenada. Se pueden definir otras funciones de plantilla que se encargará de su nullptr, o cualquier otra cosa que podría lanzar en él, y acaba de aplazar a su función principal:

template <typename T> 
inline bool operator ==(foo<T> lhs, std::nullptr_t rhs) 
{ 
    return lhs==foo<T>(rhs); 
} 

Es posible que desee hacerlo en ambos sentidos de la simetría:

template <typename T> 
inline bool operator ==(std::nullptr_t lhs,foo<T> rhs) 
{ 
    return foo<T>(lhs)==rhs; 
} 

En una nota aparte, la forma en que ha definido su función de amigo hace que operator==(foo<U>,foo<U>) sea un amigo de foo<T> incluso cuando U y T no son del mismo tipo. Probablemente no vaya a hacer mucha diferencia en la práctica, pero hay una forma técnicamente mejor de hacerlo. Implica anunciar la función de plantilla y luego hacer que la especialización para su parámetro de plantilla sea un amigo.

Aquí es un ejemplo completo:

template <typename> struct foo; 

template <typename T> 
inline bool operator==(foo<T> lhs,foo<T> rhs); 

template <typename T> 
struct foo { 
    foo(std::nullptr_t) { } 

    friend bool operator==<>(foo lhs,foo rhs); 
}; 

template <typename T> 
inline bool operator ==(foo<T> lhs,foo<T> rhs) 
{ 
    return true; 
} 

template <typename T> 
inline bool operator ==(foo<T> lhs, std::nullptr_t rhs) 
{ 
    return lhs==foo<T>(rhs); 
} 

template <typename T> 
inline bool operator ==(std::null_ptr_t lhs,foo<T> rhs) 
{ 
    return foo<T>(lhs)==rhs; 
} 

int main() { 
    foo<int> p = nullptr; 
    assert(p == nullptr); 
    assert(nullptr == p); 
    assert(p == p); 
} 
+0

Ah. Intenté con el último pero obtuve la sintaxis incorrecta (falta '<>') por lo que no funcionó. Estupendo. Aceptado para la explicación y completar el ejemplo de "mejores prácticas". Pequeño nitpick: no estoy de acuerdo en que sea una "elección estilística" hacer que 'operator ==' simétrico. Es * obligatorio * para una buena API. Una vez más, se sigue del principio de menor sorpresa y la directriz de hacer que las API sean difíciles de usar incorrectas. –

+0

@KonradRudolph: Yean, "estilística" puede no ser la mejor elección de palabras editada. –

5

Hay tres opciones posibles para usted

  • declarar y definir una nueva función amigos del tipo de RHS como std::nullptt_t

por ej

inline bool operator ==(foo<T> lhs, std::nullptr_t rhs) { return true; } 
  • O al equiparar la variable con nullptr, declare explícitamente e tipo de nullptr

por ej

assert(p == foo<int>(nullptr)); 
  • declarar y definir una nueva función amigos del tipo de RHS como void *

por ej

inline bool operator ==(foo<T> lhs, void *rhs) {   
    if (rhs == nullptr) 
     return true; 
    else 
     return false; 
    } 
+0

Ninguno de los dos es atractivo. En particular, el primero realmente no resuelve el problema porque lo mismo ocurre con '0' (también conocido como' NULL') y '(void *) 0'. El segundo acarrea un problema de biblioteca al cliente. Esto es una violación del principio de Scott Meyers de hacer que una biblioteca sea difícil de usar. –

+0

La primera opción funcionará para 0 o NULL, aunque no para (void *) 0. Sin embargo, no estoy seguro de que realmente quieras que funcione (void *) 0. Tu clase no define un constructor que tome un vacío *. –

+0

@VaughnCato Uh, cierto. –

Cuestiones relacionadas