Sobrecarga del operador [] []

93

¿Es posible sobrecargar al []operador dos veces? Para permitir, algo como esto: function[3][3](como en una matriz bidimensional).

Si es posible, me gustaría ver algún código de ejemplo.

helado
fuente
24
Por cierto, es mucho más simple y más común sobrecargar en su operator()(int, int)lugar ...
Inverso
2
¿Por qué recrear la rueda? Solo use std::vectorcon un constructor de rango: stackoverflow.com/a/25405865/610351
Geoffroy
O simplemente puede usar algo comousing array2d = std::array<std::array<int, 3>, 3>;
adembudak

Respuestas:

121

Puede sobrecargar operator[]para devolver un objeto que puede utilizar de operator[]nuevo para obtener un resultado.

class ArrayOfArrays {
public:
    ArrayOfArrays() {
        _arrayofarrays = new int*[10];
        for(int i = 0; i < 10; ++i)
            _arrayofarrays[i] = new int[10];
    }

    class Proxy {
    public:
        Proxy(int* _array) : _array(_array) { }

        int operator[](int index) {
            return _array[index];
        }
    private:
        int* _array;
    };

    Proxy operator[](int index) {
        return Proxy(_arrayofarrays[index]);
    }

private:
    int** _arrayofarrays;
};

Entonces puedes usarlo como:

ArrayOfArrays aoa;
aoa[3][5];

Este es solo un ejemplo simple, querría agregar un montón de verificación de límites y esas cosas, pero entiendes la idea.

Seth Carnegie
fuente
5
podría usar un destructor. Y Proxy::operator[]debería regresar int&no soloint
Ryan Haining
1
Es mejor usarlo std::vector<std::vector<int>>para evitar fugas de memoria y comportamientos extraños en la copia.
Jarod42
Tanto Boost's multi_arraycomo extent_genson buenos ejemplos de esta técnica. boost.org/doc/libs/1_57_0/libs/multi_array/doc/…
alfC
1
Sin embargo, const ArrayOfArrays arr; arr[3][5] = 42;será capaz de pasar la compilación y cambios arr[3][5], que es algo diferente de lo que las expectativas de los usuarios que arres const.
abcdabcd987
5
@ abcdabcd987 Eso no es correcto por un par de razones. Primero, Proxy::operator[]no devuelve una referencia en este código (asumiendo que su comentario no es una respuesta a Ryan Haining). Más importante aún, si arres constante, entonces operator[]no se puede usar. Tendría que definir una versión constante y, por supuesto, la haría regresar const Proxy. Entonces Proxysí mismo tendría métodos const y no const. Y entonces su ejemplo aún no se compilaría, y el programador estaría feliz de que todo esté bien en el universo. =)
paddy
21

Una expresión x[y][z]requiere que se x[y]evalúe como un objeto dque admita d[z].

Esto significa que x[y]debe ser un objeto con un operator[]que se evalúe como un "objeto proxy" que también admita un operator[].

Esta es la única forma de encadenarlos.

Alternativamente, sobrecargue operator()para tomar múltiples argumentos, de modo que pueda invocar myObject(x,y).

Carreras de ligereza en órbita
fuente
¿Por qué la sobrecarga de paréntesis permite obtener dos entradas pero no puede hacer lo mismo con los corchetes?
A. Frenzy
20

Para una matriz bidimensional, específicamente, puede salirse con la suya con una sobrecarga de operador [] que devuelve un puntero al primer elemento de cada fila.

Luego, puede usar el operador de indexación incorporado para acceder a cada elemento dentro de la fila.

Bo Persson
fuente
4
Me parece la solución más práctica y eficaz. Me pregunto por qué no obtiene más votos, tal vez porque no tiene el código llamativo.
Yigal Reiss
16

Es posible si devuelve algún tipo de clase de proxy en la primera llamada []. Sin embargo, hay otra opción: puede sobrecargar el operador () que puede aceptar cualquier número de argumentos ( function(3,3)).

Juan
fuente
10

Un enfoque es utilizar std::pair<int,int>:

class Array2D
{
    int** m_p2dArray;
public:
    int operator[](const std::pair<int,int>& Index)
    {
       return m_p2dArray[Index.first][Index.second];
    }
};

int main()
{
    Array2D theArray;
    pair<int, int> theIndex(2,3);
    int nValue;
    nValue = theArray[theIndex];
}

Por supuesto, es posible que typedefelpair<int,int>

Ajay
fuente
9
Esto se vuelve mucho más atractivo con C ++ 11 y la inicialización de llaves. Ahora puedes escribirnValue = theArray[{2,3}];
Martin Bonner apoya a Monica
5

Puede usar un objeto proxy, algo como esto:

#include <iostream>

struct Object
{
    struct Proxy
    {
        Object *mObj;
        int mI;

        Proxy(Object *obj, int i)
        : mObj(obj), mI(i)
        {
        }

        int operator[](int j)
        {
            return mI * j;
        }
    };

    Proxy operator[](int i)
    {
        return Proxy(this, i);
    }
};

int main()
{
    Object o;
    std::cout << o[2][3] << std::endl;
}
Nodo
fuente
4

Es ll ser bueno si se puede me informara function, function[x]y function[x][y]está. Pero de todos modos déjame considerarlo como un objeto declarado en algún lugar como

SomeClass function;

(Como dijiste que es una sobrecarga del operador, creo que no te interesará una matriz como SomeClass function[16][32];)

También lo functiones una instancia de tipo SomeClass. Luego busque la declaración de SomeClasspara el tipo de retorno de operator[]sobrecarga, como

ReturnType operator[](ParamType);

Entonces function[x]tendrá el tipo ReturnType. Vuelva a buscar ReturnTypela operator[]sobrecarga. Si existe tal método, puede usar la expresión function[x][y].

Tenga en cuenta, a diferencia de function(x, y), function[x][y]son 2 llamadas separadas. Por lo tanto, es difícil para el compilador o el tiempo de ejecución garantiza la atomicidad a menos que use un bloqueo en el contexto. Un ejemplo similar es, libc dice que printfes atómico, mientras que las llamadas sucesivas al operator<<flujo de salida sobrecargado no lo son. Una declaración como

std::cout << "hello" << std::endl;

podría tener un problema en la aplicación de subprocesos múltiples, pero algo como

printf("%s%s", "hello", "\n");

está bien.

neurona
fuente
2
#include<iostream>

using namespace std;

class Array 
{
     private: int *p;
     public:
          int length;
          Array(int size = 0): length(size)
          {
                p=new int(length);
          }
          int& operator [](const int k)
          {
               return p[k];
          }
};
class Matrix
{
      private: Array *p;
      public: 
            int r,c;
            Matrix(int i=0, int j=0):r(i), c(j)
            {
                 p= new Array[r];
            }
            Array& operator [](const int& i)
            {
                 return p[i];
            }
};

/*Driver program*/
int main()
{
    Matrix M1(3,3); /*for checking purpose*/
    M1[2][2]=5;
}
Kaustav Ray
fuente
2
struct test
{
    using array_reference = int(&)[32][32];

    array_reference operator [] (std::size_t index)
    {
        return m_data[index];
    }

private:

    int m_data[32][32][32];
};

Encontré mi propia solución simple para esto.

Grandstack
fuente
2
template<class F>
struct indexer_t{
  F f;
  template<class I>
  std::result_of_t<F const&(I)> operator[](I&&i)const{
    return f(std::forward<I>(i))1;
  }
};
template<class F>
indexer_t<std::decay_t<F>> as_indexer(F&& f){return {std::forward<F>(f)};}

Esto le permite tomar una lambda y producir un indexador (con []soporte).

Suponga que tiene un operator()que admite pasar ambas coordenadas en onxe como dos argumentos. Ahora el [][]soporte de escritura es solo:

auto operator[](size_t i){
  return as_indexer(
    [i,this](size_t j)->decltype(auto)
    {return (*this)(i,j);}
  );
}

auto operator[](size_t i)const{
  return as_indexer(
    [i,this](size_t j)->decltype(auto)
    {return (*this)(i,j);}
  );
}

Y hecho. No se requiere clase personalizada.

Yakk - Adam Nevraumont
fuente
2

Si, en lugar de decir una [x] [y], le gustaría decir una [{x, y}], puede hacer lo siguiente:

struct Coordinate {  int x, y; }

class Matrix {
    int** data;
    operator[](Coordinate c) {
        return data[c.y][c.x];
    }
}
Erel Segal-Halevi
fuente
1

Es posible sobrecargar múltiples [] usando un manejador de plantillas especializado. Solo para mostrar cómo funciona:

#include <iostream>
#include <algorithm>
#include <numeric>
#include <tuple>
#include <array>

using namespace std;

// the number '3' is the number of [] to overload (fixed at compile time)
struct TestClass : public SubscriptHandler<TestClass,int,int,3> {

    // the arguments will be packed in reverse order into a std::array of size 3
    // and the last [] will forward them to callSubscript()
    int callSubscript(array<int,3>& v) {
        return accumulate(v.begin(),v.end(),0);
    }

};

int main() {


    TestClass a;
    cout<<a[3][2][9];  // prints 14 (3+2+9)

    return 0;
}

Y ahora la definición de SubscriptHandler<ClassType,ArgType,RetType,N>hacer que el código anterior funcione. Solo muestra cómo se puede hacer. Esta solución es óptima y no está libre de errores (no segura para subprocesos, por ejemplo).

#include <iostream>
#include <algorithm>
#include <numeric>
#include <tuple>
#include <array>

using namespace std;

template <typename ClassType,typename ArgType,typename RetType, int N> class SubscriptHandler;

template<typename ClassType,typename ArgType,typename RetType, int N,int Recursion> class SubscriptHandler_ {

    ClassType*obj;
    array<ArgType,N+1> *arr;

    typedef SubscriptHandler_<ClassType,ArgType,RetType,N,Recursion-1> Subtype;

    friend class SubscriptHandler_<ClassType,ArgType,RetType,N,Recursion+1>;
    friend class SubscriptHandler<ClassType,ArgType,RetType,N+1>;

public:

    Subtype operator[](const ArgType& arg){
        Subtype s;
        s.obj = obj;
        s.arr = arr;
        arr->at(Recursion)=arg;
        return s;
    }
};

template<typename ClassType,typename ArgType,typename RetType,int N> class SubscriptHandler_<ClassType,ArgType,RetType,N,0> {

    ClassType*obj;
    array<ArgType,N+1> *arr;

    friend class SubscriptHandler_<ClassType,ArgType,RetType,N,1>;
    friend class SubscriptHandler<ClassType,ArgType,RetType,N+1>;

public:

    RetType operator[](const ArgType& arg){
        arr->at(0) = arg;
        return obj->callSubscript(*arr);
    }

};


template<typename ClassType,typename ArgType,typename RetType, int N> class SubscriptHandler{

    array<ArgType,N> arr;
    ClassType*ptr;
    typedef SubscriptHandler_<ClassType,ArgType,RetType,N-1,N-2> Subtype;

protected:

    SubscriptHandler() {
        ptr=(ClassType*)this;
    }

public:

    Subtype operator[](const ArgType& arg){
        Subtype s;
        s.arr=&arr;
        s.obj=ptr;
        s.arr->at(N-1)=arg;
        return s;
    }
};

template<typename ClassType,typename ArgType,typename RetType> struct SubscriptHandler<ClassType,ArgType,RetType,1>{
    RetType operator[](const ArgType&arg) {
        array<ArgType,1> arr;
        arr.at(0)=arg;
        return ((ClassType*)this)->callSubscript(arr);
    }
};
Frédéric Terrazzoni
fuente
0

Con a std::vector<std::vector<type*>>, puede construir el vector interior usando un operador de entrada personalizado que itera sobre sus datos y devuelve un puntero a cada dato.

Por ejemplo:

size_t w, h;
int* myData = retrieveData(&w, &h);

std::vector<std::vector<int*> > data;
data.reserve(w);

template<typename T>
struct myIterator : public std::iterator<std::input_iterator_tag, T*>
{
    myIterator(T* data) :
      _data(data)
    {}
    T* _data;

    bool operator==(const myIterator& rhs){return rhs.data == data;}
    bool operator!=(const myIterator& rhs){return rhs.data != data;}
    T* operator*(){return data;}
    T* operator->(){return data;}

    myIterator& operator++(){data = &data[1]; return *this; }
};

for (size_t i = 0; i < w; ++i)
{
    data.push_back(std::vector<int*>(myIterator<int>(&myData[i * h]),
        myIterator<int>(&myData[(i + 1) * h])));
}

Ejemplo vivo

Esta solución tiene la ventaja de proporcionarle un contenedor STL real, por lo que puede utilizar bucles especiales para, algoritmos STL, etc.

for (size_t i = 0; i < w; ++i)
  for (size_t j = 0; j < h; ++j)
    std::cout << *data[i][j] << std::endl;

Sin embargo, crea vectores de punteros, por lo que si está utilizando estructuras de datos pequeñas como esta, puede copiar directamente el contenido dentro de la matriz.

Geoffroy
fuente
0

Código de muestra:

template<class T>
class Array2D
{
public:
    Array2D(int a, int b)  
    {
        num1 = (T**)new int [a*sizeof(int*)];
        for(int i = 0; i < a; i++)
            num1[i] = new int [b*sizeof(int)];

        for (int i = 0; i < a; i++) {
            for (int j = 0; j < b; j++) {
                num1[i][j] = i*j;
            }
        }
    }
    class Array1D
    {
    public:
        Array1D(int* a):temp(a) {}
        T& operator[](int a)
        {
            return temp[a];
        }
        T* temp;
    };

    T** num1;
    Array1D operator[] (int a)
    {
        return Array1D(num1[a]);
    }
};


int _tmain(int argc, _TCHAR* argv[])
{
    Array2D<int> arr(20, 30);

    std::cout << arr[2][3];
    getchar();
    return 0;
}
Anant Rai
fuente
0

vector <vector <T>> o T ** se requiere solo cuando tiene filas de longitud variable y es demasiado ineficiente en términos de uso de memoria / asignaciones si necesita una matriz rectangular, ¡considere hacer algunas matemáticas en su lugar! ver método at ():

template<typename T > class array2d {

protected:
    std::vector< T > _dataStore;
    size_t _sx;

public:
    array2d(size_t sx, size_t sy = 1): _sx(sx), _dataStore(sx*sy) {}
    T& at( size_t x, size_t y ) { return _dataStore[ x+y*sx]; }
    const T& at( size_t x, size_t y ) const { return _dataStore[ x+y*sx]; }
    const T& get( size_t x, size_t y ) const { return at(x,y); }
    void set( size_t x, size_t y, const T& newValue ) { at(x,y) = newValue; }
};
xakepp35
fuente
0

Con C ++ 11 y la biblioteca estándar, puede hacer una matriz bidimensional muy agradable en una sola línea de código:

std::array<std::array<int, columnCount>, rowCount> myMatrix {0};

std::array<std::array<std::string, columnCount>, rowCount> myStringMatrix;

std::array<std::array<Widget, columnCount>, rowCount> myWidgetMatrix;

Al decidir que la matriz interna representa filas, accede a la matriz con una myMatrix[y][x]sintaxis:

myMatrix[0][0] = 1;
myMatrix[0][3] = 2;
myMatrix[3][4] = 3;

std::cout << myMatrix[3][4]; // outputs 3

myStringMatrix[2][4] = "foo";
myWidgetMatrix[1][5].doTheStuff();

Y puede usar ranged- forpara salida:

for (const auto &row : myMatrix) {
  for (const auto &elem : row) {
    std::cout << elem << " ";
  }
  std::cout << std::endl;
}

(Decidir las arraycolumnas de representación interna permitiría una foo[x][y]sintaxis, pero necesitaría usar for(;;)bucles más torpes para mostrar la salida).

Jack Deeth
fuente
0

Mis 5 centavos.

Intuitivamente sabía que necesitaba hacer mucho código repetitivo.

Es por eso que, en lugar de operator [], hice operador sobrecargado (int, int). Luego, en el resultado final, en lugar de m [1] [2], hice m (1,2)

Sé que es algo DIFERENTE, pero sigue siendo muy intuitivo y parece un guión matemático.

Mella
fuente
0

La solución más rápida y sencilla:

class Matrix
{
public:
  float m_matrix[4][4];

// for statements like matrix[0][0] = 1;
  float* operator [] (int index) 
  {
    return m_matrix[index];
  }

// for statements like matrix[0][0] = otherMatrix[0][0];
  const float* operator [] (int index) const 
  {
    return m_matrix[index];
  }

};
Vegeta
fuente