Przeciążenie operatora [] []

93

Czy można []dwukrotnie przeciążać operatora? Aby pozwolić, coś takiego: function[3][3](jak w dwuwymiarowej tablicy).

Jeśli to możliwe, chciałbym zobaczyć przykładowy kod.

icepopo
źródło
24
Przy okazji, operator()(int, int)zamiast tego przeciążanie jest znacznie prostsze i bardziej powszechne ...
Odwrócenie
2
Po co regenerować koło? Po prostu użyj std::vectorz konstruktorem zakresu: stackoverflow.com/a/25405865/610351
Geoffroy,
Lub możesz po prostu użyć czegoś takiegousing array2d = std::array<std::array<int, 3>, 3>;
adembudak

Odpowiedzi:

121

Możesz przeciążać, operator[]aby zwrócić obiekt, którego możesz użyć operator[]ponownie, aby uzyskać wynik.

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;
};

Następnie możesz go używać tak:

ArrayOfArrays aoa;
aoa[3][5];

To tylko prosty przykład, chciałbyś dodać kilka sprawdzeń granic i innych rzeczy, ale masz pomysł.

Seth Carnegie
źródło
5
przydałby się destruktor. I Proxy::operator[]powinien wrócić int&nie tylkoint
Ryan Haining
1
Lepiej używać, std::vector<std::vector<int>>aby uniknąć memleak i dziwnego zachowania na kopii.
Jarod42
Zarówno Boost, jak multi_arrayi extent_gensą dobrymi przykładami tej techniki. boost.org/doc/libs/1_57_0/libs/multi_array/doc/…
alfC
1
Jednak const ArrayOfArrays arr; arr[3][5] = 42;będą mogli przechodzić kompilację i zmiany arr[3][5], która jest w jakiś sposób różni się od oczekiwań użytkowników, że arrjest const.
abcdabcd987
5
@ abcdabcd987 To nie jest poprawne z kilku powodów. Po pierwsze, Proxy::operator[]nie zwraca odwołania w tym kodzie (zakładając, że Twój komentarz nie jest odpowiedzią dla Ryana Haininga). Co ważniejsze, jeśli arrjest stała, operator[]nie można jej użyć. Musiałbyś zdefiniować wersję const i oczywiście sprawiłbyś, że wróci const Proxy. Wtedy Proxysam miałby metody const i non-const. Twój przykład nadal by się nie kompilował, a programista byłby szczęśliwy, że we wszechświecie wszystko jest w porządku. =)
paddy
21

Wyrażenie x[y][z]wymaga, aby x[y]wartościować do obiektu, dktóry obsługuje d[z].

Oznacza to, że x[y]powinien to być obiekt z atrybutem an, operator[]którego wynikiem jest „obiekt proxy”, który również obsługuje rozszerzenie operator[].

Tylko w ten sposób można je połączyć.

Alternatywnie możesz przeciążać, operator()aby pobrać wiele argumentów, tak aby można było wywołać myObject(x,y).

Lekkość wyścigów na orbicie
źródło
Dlaczego przeciążenie nawiasami umożliwia uzyskanie dwóch danych wejściowych, ale nie można zrobić tego samego z nawiasami?
A. Frenzy
20

W szczególności w przypadku tablic dwuwymiarowych można uniknąć przeciążenia pojedynczego operatora [], które zwraca wskaźnik do pierwszego elementu każdego wiersza.

Następnie możesz użyć wbudowanego operatora indeksowania, aby uzyskać dostęp do każdego elementu w wierszu.

Bo Persson
źródło
4
Wydaje mi się, że jest to najbardziej praktyczne i wydajne rozwiązanie. Ciekawe, dlaczego nie ma więcej głosów - może dlatego, że nie ma przyciągającego wzrok kodu.
Yigal Reiss
16

Jest to możliwe, jeśli w pierwszym wywołaniu [] zwrócisz jakąś klasę proxy. Jest jednak inna opcja: możesz przeciążyć operator (), który może przyjąć dowolną liczbę argumentów ( function(3,3)).

Jan
źródło
10

Jedną z metod jest użycie 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];
}

Oczywiście, możesztypedefpair<int,int>

Ajay
źródło
9
Staje się to o wiele bardziej atrakcyjne dzięki C ++ 11 i inicjalizacji nawiasów klamrowych. Teraz możesz pisaćnValue = theArray[{2,3}];
Martin Bonner wspiera Monikę
5

Możesz użyć obiektu proxy, czegoś takiego:

#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;
}
Węzeł
źródło
4

To „ll być wielki, jeśli możesz dać mi znać, co function, function[x]i function[x][y]są. W każdym razie pozwólcie, że uznam to za obiekt zadeklarowany gdzieś w podobny sposób

SomeClass function;

(Ponieważ powiedziałeś, że to przeciążenie operatorów, myślę, że nie będziesz zainteresowany tablicami takimi jak SomeClass function[16][32];)

Tak functionjest w przypadku typu SomeClass. Następnie wyszukaj deklarację SomeClasszwracanego typu operator[]przeciążenia, tak jak

ReturnType operator[](ParamType);

Wtedy function[x]będzie miał typ ReturnType. Ponownie spojrzeć ReturnTypena operator[]przeciążenia. Jeśli istnieje taka metoda, możesz użyć wyrażenia function[x][y].

Uwaga, w odróżnieniu function(x, y), function[x][y]są 2 oddzielne rozmowy. Tak więc jest to trudne dla kompilatora lub środowiska wykonawczego, które gwarantuje atomowość, chyba że użyjesz blokady w kontekście. Podobny przykład jest taki, że libc mówi, że printfjest atomowy, podczas gdy kolejne wywołania przeciążonego operator<<strumienia wyjściowego nie. Oświadczenie jak

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

może mieć problem w aplikacji wielowątkowej, ale coś w rodzaju

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

jest w porządku.

neuront
źródło
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
źródło
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];
};

Znalazłem własne proste rozwiązanie tego problemu.

Grandstack
źródło
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)};}

Dzięki temu możesz wziąć lambdę i utworzyć indeksator (z []obsługą).

Załóżmy, że masz operator()argument, który obsługuje przekazywanie obu współrzędnych w onxe jako dwóch argumentów. Teraz pisanie [][]wsparcia to po prostu:

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);}
  );
}

I zrobione. Nie jest wymagana żadna klasa niestandardowa.

Yakk - Adam Nevraumont
źródło
2

Jeśli zamiast mówić a [x] [y], chcesz powiedzieć [{x, y}], możesz zrobić to w ten sposób:

struct Coordinate {  int x, y; }

class Matrix {
    int** data;
    operator[](Coordinate c) {
        return data[c.y][c.x];
    }
}
Erel Segal-Halevi
źródło
1

Możliwe jest przeciążenie wielu [] za pomocą wyspecjalizowanego programu obsługi szablonów. Aby pokazać, jak to działa:

#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;
}

A teraz definicja, SubscriptHandler<ClassType,ArgType,RetType,N>aby poprzedni kod działał. Pokazuje tylko, jak można to zrobić. To rozwiązanie jest optymalne i wolne od błędów (na przykład nie jest bezpieczne dla wątków).

#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
źródło
0

Za pomocą a std::vector<std::vector<type*>>można zbudować wektor wewnętrzny przy użyciu niestandardowego operatora wejściowego, który iteruje po danych i zwraca wskaźnik do każdej z nich.

Na przykład:

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])));
}

Przykład na żywo

To rozwiązanie ma tę zaletę, że zapewnia prawdziwy kontener STL, więc możesz użyć specjalnego dla pętli, algorytmów STL i tak dalej.

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

Jednak tworzy wektory wskaźników, więc jeśli używasz małych struktur danych, takich jak ta, możesz bezpośrednio skopiować zawartość wewnątrz tablicy.

Geoffroy
źródło
0

Przykładowy kod:

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
źródło
0

vector <vector <T>> lub T ** jest wymagane tylko wtedy, gdy masz wiersze o zmiennej długości i są one zbyt nieefektywne pod względem wykorzystania / alokacji pamięci, jeśli potrzebujesz tablicy prostokątnej, rozważ zamiast tego trochę matematyki! patrz metoda ():

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
źródło
0

Używając C ++ 11 i Biblioteki Standardowej, możesz stworzyć bardzo ładną dwuwymiarową tablicę w jednej linii kodu:

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;

Decydując, że wewnętrzna macierz reprezentuje wiersze, uzyskujesz dostęp do macierzy za pomocą myMatrix[y][x]składni:

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();

I możesz użyć ranged- fordo wyjścia:

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

(Zdecydowanie, że wewnętrzna arrayreprezentuje kolumny pozwoliłaby na foo[x][y]składnię, ale for(;;)do wyświetlenia danych wyjściowych konieczne byłoby użycie bardziej niezgrabnych pętli).

Jack Deeth
źródło
0

Moje 5 centów.

Intuicyjnie wiedziałem, że muszę zrobić dużo standardowego kodu.

Dlatego zamiast operatora [] zrobiłem przeciążony operator (int, int). Wtedy w ostatecznym wyniku zamiast m [1] [2] zrobiłem m (1,2)

Wiem, że to INNA rzecz, ale nadal jest bardzo intuicyjny i wygląda jak skrypt matematyczny.

Nacięcie
źródło
0

Najkrótsze i najłatwiejsze rozwiązanie:

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
źródło