2012-05-18 21 views
40

Tengo una clase que utiliza una clase anidada, y quiero usar la clase anidada operator<< para definir operator<< en la clase superior. Aquí es cómo mi código es el siguiente:Operador sobrecarga <<: no puede vincular lvalue a 'std :: basic_ostream <char> &&'

#include <memory> 
#include <iostream> 

template<typename T> 
struct classA { 
    struct classB 
    { 
    template<typename U> 
    friend inline std::ostream& operator<< (std::ostream &out, 
              const typename classA<U>::classB &b); 
    }; 

    classB root; 

    template<typename U> 
    friend std::ostream& operator<< (std::ostream &out, 
            const classA<U> &tree); 
}; 

template<typename T> 
inline std::ostream& operator<< (std::ostream &out, 
           const classA<T> &tree) 
{ 
    out << tree.root; 
    return out; 
} 

template<typename T> 
inline std::ostream& operator<< (std::ostream &out, 
           const typename classA<T>::classB &b) 
{ 
    return out; 
} 

int main() 
{ 
    classA<int> a; 
    std::cout << a; 
} 
  • Al compilar sin soporte para C++ 11, la definición de operador < < para la clase interna no parece ser encontrado por el compilador:

    so.hpp:24:7: error: no match for ‘operator<<’ in ‘out << tree.classA<int>::root’ 
    so.hpp:24:7: note: candidates are: ... 
    
  • con GCC 4.6 y 4.7 al compilar con std = C++ 0x:

    so.hpp:21:3: error: cannot bind ‘std::ostream {aka std::basic_ostream<char>}’ lvalue to ‘std::basic_ostream<char>&&’ 
    In file included from /usr/include/c++/4.7/iostream:40:0, 
           from so.hpp:2: 
    /usr/include/c++/4.7/ostream:600:5: error: initializing argument 1 of ‘std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char; _Traits = std::char_traits<char>; _Tp = classA<int>::classB]’ 
    

¿Alguien me puede decir por qué este código no es legal y cuál es la mejor manera de hacer lo que quiero?

Respuesta

23

Bo proporcionó la razón por qué ocurre esto (el tipo T no es deducible en la llamada a la anidada operator<<. Una solución sencilla para esto, y algo que recomiendo en general, no sólo aquí, no se Befriending . una plantilla, sino más bien una sola función libre para que tendrá que definen la función en línea:.

template<typename T> 
struct classA { 
    struct classB 
    { 
    friend inline std::ostream& operator<< (std::ostream &out, 
              const classB &b) { 
     // definition goes here 
    } 
    }; 

    classB root; 

    friend std::ostream& operator<< (std::ostream &out, 
            const classA<U> &tree) { 
     // definition goes here 
    } 
}; 

Hay un par de diferencias entre los dos enfoques la más importante es que este enfoque hacer que el compilador defina una sobrecarga sin plantilla para operator<< para cada instanciación de la plantilla, que debido a que ya no es una plantilla, no depende de deducir los argumentos.Otros efectos secundarios son que el enfoque es un poco más ajustado (solo se está haciendo amigo de una función, mientras que en su enfoque inicial se hizo amigo de la plantilla y todas las instancias posibles (que pueden usarse como una escapatoria para acceder a su clase internos). Finalmente las funciones así definidas sólo serán encontrados a través de la ADL, por lo que hay menos sobrecarga de operator<< para el compilador para considerar cuando el argumento no es ClassA<T> o ClassA<T>::ClassB.


¿Cómo puede ganar el acceso a su enfoque

namespace { 
    struct intruder { 
     ClassA & ref; 
     intruder(ClassA& r) : ref(r) {} 
    }; 
    template <> 
    std::ostream& operator<< <intruder>(std::ostream& _, ClassA<intruder> const& i) { 
     std::cout << i.ref.private_member << std::endl; 
     return _; 
    } 
} 

Alternativa

Alternativamente, puede hacerse amigo de una especialización particular de una plantilla. Eso resolverá el problema intruder, ya que solo estará abierto a operator<< a ClassA<intruder>, lo que tiene un impacto mucho menor. Pero esto no resolverá su problema en particular, ya que el tipo aún no sería deducible.

+0

Gracias por proporcionarnos una solución. – Antoine

+0

Lo veo como un * mejor diseño * en lugar de una * solución alternativa *. Tiene su lado negativo (no se puede tomar la dirección de la función de amigo declarada dentro de la clase de plantilla), pero en todas las otras cuentas es una mejor opción proporcionar operadores de funciones libres ... –

+0

Este es el [Hacer nuevos amigos] (https : idioma: //en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Making_New_Friends), no es realmente una solución. – TBBle

28

usted tiene un problema con un "non-deducible context" en este operador

template<typename T> 
inline std::ostream& operator<< (std::ostream &out, 
           const typename classA<T>::classB &b) 
{ 
    return out; 
} 

el compilador no puede averiguar qué valores de T resultarán en una classB que coincide con el parámetro que desea pasar. ¡Entonces esta plantilla no es considerada!

En el modo C++ 11, el compilador pasa luego a encontrar una coincidencia estrecha de la librería estándar

operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) 

donde se puede partido _Tp a casi cualquier tipo, incluyendo classA<T>::classB, pero las notas que el primer parámetro no coincide.

+1

El comentario sobre la coincidencia cercana dejó en claro por qué estaba teniendo un problema similar. –

2

Prueba esto:

template<typename T> 
inline std::ostream& operator<< (std::ostream &out, 
          const classA<T> &tree) 
{ 
    //out << tree.root; 
    ::operator<<(out, tree.root); 
    return out; 
} 

y por lo que recibirá una confesión directa de la ineptitud:

test.cpp:34:3: error: no matching function for call to ‘operator<<(std::ostream&, const classA<int>::classB&)’ 
test.cpp:34:3: note: candidates are: 
test.cpp:23:22: note: template<class T> std::ostream& operator<<(std::ostream&, const  typename classA<T>::classB&) 
test.cpp:30:22: note: template<class T> std::ostream& operator<<(std::ostream&, const classA<T>&) 

Solución: tal vez se puede utilizar una función miembro en ClassB anidado, y usarlo en lugar de operador < < ... Por supuesto, esa solución tiene una multitud de inconvenientes, pero puede sacarlo de esta prisa.

+1

Siempre puede llamar explícitamente a ':: operator << (out, tree.root)' en su lugar, pero creo que las funciones de miembro con nombre ahora se ven más atractivas. – Useless

Cuestiones relacionadas