2010-08-11 15 views
10

Menos mal que fue un título largo.Operador sobrecargado [] en la clase de plantilla en C++ con versiones const/nonconst

Aquí está mi problema. Tengo una clase de plantilla en C++ y estoy sobrecargando el operador []. Tengo tanto una constante y una versión no constante, con la versión no constante retorno por referencia de manera que los elementos de la clase se pueden cambiar como tan:

myobject[1] = myvalue; 

Todo esto funciona hasta que yo utilizo un booleano como el parámetro de la plantilla. Aquí está un ejemplo completo que muestra el error:

#include <string> 
#include <vector> 
using namespace std; 

template <class T> 
class MyClass 
{ 
    private: 
     vector<T> _items; 

    public: 

     void add(T item) 
     { 
      _items.push_back(item); 
     } 

     const T operator[](int idx) const 
     { 
      return _items[idx]; 
     } 

     T& operator[](int idx) 
     { 
      return _items[idx]; 
     } 

}; 


int main(int argc, char** argv) 
{ 
    MyClass<string> Test1;  // Works 
    Test1.add("hi"); 
    Test1.add("how are"); 
    Test1[1] = "you?"; 


    MyClass<int> Test2;   // Also works 
    Test2.add(1); 
    Test2.add(2); 
    Test2[1] = 3; 


    MyClass<bool> Test3;  // Works up until... 
    Test3.add(true); 
    Test3.add(true); 
    Test3[1] = false;   // ...this point. :(

    return 0; 
} 

El error es un error del compilador y el mensaje es:

error: invalid initialization of non-const reference of type ‘bool&’ from a temporary of type ‘std::_Bit_reference’ 

He leído y encontré que STL utiliza algunos tipos de datos temporales, pero No entiendo por qué funciona con todo, excepto un bool.

Cualquier ayuda sobre esto sería apreciada.

+1

leer este artículo: http://www.gotw.ca/publications/mill09.htm –

+0

Puede ser fijo. Vea abajo. Solo necesita devolver el mismo tipo que el operador [] devuelve (que generalmente es T & (pero no siempre como en el caso del vector )) –

Respuesta

9

Porque vector<bool> está especializado en STL y no cumple con los requisitos de un contenedor estándar.

Herb Sutter habla de más de un artículo GOTW: http://www.gotw.ca/gotw/050.htm

+0

Increíble. He estado usando STL durante años y nunca me he encontrado con esto. Gracias a Dios que resumí la clase vectorial, o estaría en un mundo de dolor en este momento. Gracias por la respuesta. – riwalk

+0

Todo es cierto y explica por qué no funciona como se describe anteriormente. Pero eso no significa que el código anterior no esté disponible. Los problemas solo surgen realmente cuando comienzas a tratar de obtener la dirección de cualquier elemento en particular para que las cosas comiencen a desmoronarse (lo cual no es el caso aquí). –

+0

Creo que el problema más grande es, como lo menciona ese artículo, que el STL ramifica su comportamiento dependiendo de la entrada. Esto significa que el comportamiento puede ser drásticamente diferente dependiendo de la entrada. Codificación muy pobre en mi opinión. – riwalk

5

A vector<bool> no está implementado como todos los demás vectores, y tampoco funciona como ellos. Es mejor que simplemente no lo use, y no se preocupe si su código no puede manejar sus muchas peculiaridades. Se lo considera principalmente como una mala cosa, impuesta por algunos miembros irreflexivos del comité estándar de C++.

+0

E incluso los miembros del Comité Estándar pronto se dieron cuenta de su error y trataron de eliminarlo, pero tal es la naturaleza de los Estándares, está atorado allí ... –

6

Un vector<bool> no es un contenedor real. Su código intenta efectivamente devolver una referencia a un solo bit, lo que no está permitido. Si cambia su contenedor a deque, creo que obtendrá el comportamiento que espera.

+0

+1 ¡Lo haría :)! –

+0

+1 deque funcionó como un encanto :) – riwalk

4

Algunos cambios a su clase deben solucionarlo.

template <class T> 
class MyClass 
{ 
    private: 
     vector<T> _items; 

    public: 

     // This works better if you pass by const reference. 
     // This allows the compiler to form temorary objects and pass them to the method. 
     void add(T const& item) 
     { 
      _items.push_back(item); 
     } 

     // For the const version of operator[] you were returning by value. 
     // Normally I would have returned by const ref. 

     // In normal situations the result of operator[] is T& or T const& 
     // But in the case of vector<bool> it is special 
     // (because apparently we want to pack a bool vector) 

     // But technically the return type from vector is `reference` (not T&) 
     // so it you use that it should compensate for the odd behavior of vector<bool> 
     // Of course const version is `const_reference` 

     typename vector<T>::const_reference operator[](int idx) const 
     { 
      return _items[idx]; 
     } 

     typename vector<T>::reference operator[](int idx) 
     { 
      return _items[idx]; 
     } 
}; 
+0

+1 para encontrar una solución real :). Probablemente no vaya por esta ruta, simplemente porque no me gusta hacer lo imposible para satisfacer los matices de una biblioteca de terceros. La clase que estoy escribiendo resume STL, por lo que el usuario no debería poder ver ningún artefacto restante de STL en la API: P. ¡Gracias por el aporte! – riwalk

1

Como las otras respuestas señalan, se proporciona una especialización para optimizar la asignación de espacio en el caso de vectores < bool>.

Sin embargo, aún puede hacer que su código sea válido si utiliza vector :: reference en lugar de T &. De hecho, es una buena práctica usar container :: reference al hacer referencia a los datos contenidos en un contenedor STL.

T& operator[](int idx) 

convierte

typename vector<T>::reference operator[](int idx) 

De allí supuesto es también un typedef para referencia constante:

const T operator[](int idx) const 

y éste se convierte en (la eliminación de la copia extra inútil)

typename vector<T>::const_reference operator[](int idx) const 
1

El motivo del error es que vector<bool> está especializado para empacar los valores booleanos almacenados en y vector<bool>::operator[] devuelve algún tipo de proxy que le permite acceder al valor.

No creo que una solución sería devolver el mismo tipo que vector<bool>::operator[] porque entonces solo estaría copiando el lamentable comportamiento especial de su contenedor.

Si desea seguir usando vector como el tipo subyacente, creo que el problema bool podría ser parcheado mediante el uso de un vector<MyBool> lugar cuando se crea una instancia con MyClassbool.

Se podría tener este aspecto:

#include <string> 
#include <vector> 
using namespace std; 

namespace detail 
{ 
    struct FixForBool 
    { 
     bool value; 
     FixForBool(bool b): value(b) {} 
     operator bool&() { return value; } 
     operator const bool&() const { return value; } 
    }; 

    template <class T> 
    struct FixForValueTypeSelection 
    { 
     typedef T type; 
    }; 

    template <> 
    struct FixForValueTypeSelection<bool> 
    { 
     typedef FixForBool type; 
    }; 

} 

template <class T> 
class MyClass 
{ 
    private: 
     vector<typename detail::FixForValueTypeSelection<T>::type> _items; 

    public: 

     void add(T item) 
     { 
      _items.push_back(item); 
     } 

     const T operator[](int idx) const 
     { 
      return _items[idx]; 
     } 

     T& operator[](int idx) 
     { 
      return _items[idx]; 
     } 

}; 


int main(int argc, char** argv) 
{ 
    MyClass<string> Test1;  // Works 
    Test1.add("hi"); 
    Test1.add("how are"); 
    Test1[1] = "you?"; 


    MyClass<int> Test2;   // Also works 
    Test2.add(1); 
    Test2.add(2); 
    Test2[1] = 3; 


    MyClass<bool> Test3;  // Works up until... 
    Test3.add(true); 
    Test3.add(true); 
    Test3[1] = false;   // ...this point. :(

    return 0; 
} 
+0

Elegante ... Probablemente no lo use (simplemente porque no tengo ningún archivo adjunto al vector, esta clase es un vector de abstracción de todos modos), pero esa es una solución elegante si se debe usar el vector. – riwalk

+0

Yo mismo estoy teniendo segundas dudas, sin embargo. Aún así, no recomendaría tomar la dirección con '& my_vec [0]'. – UncleBens

+0

Trabajar con las direcciones de elementos en STL es tan arriesgado como trabajar con iteradores en STL. Cada vez que se invalida un iterador (al agregar un nuevo elemento), la dirección puede ser invalidada. De cualquier manera, esta clase de ejemplo solo se ajusta a la clase vector <>. Al hacer eso, simplemente pasa el mismo nivel de riesgo que la clase vector <>. – riwalk

Cuestiones relacionadas