Surcharge de l'opérateur [] []

90

Est-il possible de surcharger l' []opérateur deux fois? Pour permettre, quelque chose comme ceci: function[3][3](comme dans un tableau à deux dimensions).

Si c'est possible, j'aimerais voir un exemple de code.

icepopo
la source
22
Btw, c'est beaucoup plus simple et plus courant de surcharger à la operator()(int, int)place ...
Inverse
2
Pourquoi recréer la roue? Utilisez simplement std::vectoravec un constructeur de plage: stackoverflow.com/a/25405865/610351
Geoffroy
Ou vous pouvez simplement utiliser quelque chose commeusing array2d = std::array<std::array<int, 3>, 3>;
adem

Réponses:

118

Vous pouvez surcharger operator[]pour renvoyer un objet sur lequel vous pouvez utiliser à operator[]nouveau pour obtenir un résultat.

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

Ensuite, vous pouvez l'utiliser comme:

ArrayOfArrays aoa;
aoa[3][5];

Ceci n'est qu'un exemple simple, vous voudriez ajouter un tas de vérifications de limites et d'autres choses, mais vous voyez l'idée.

Seth Carnegie
la source
5
pourrait utiliser un destructeur. Et Proxy::operator[]devrait revenir int&non seulementint
Ryan Haining
1
Mieux vaut utiliser std::vector<std::vector<int>>pour éviter les memleak et les comportements étranges sur la copie.
Jarod42
Les deux Boost multi_arrayet extent_gensont de bons exemples de cette technique. boost.org/doc/libs/1_57_0/libs/multi_array/doc/…
alfC
1
Cependant, const ArrayOfArrays arr; arr[3][5] = 42;sera en mesure de passer la compilation et des changements arr[3][5], ce qui est quelque peu différente de ce que l' attente des utilisateurs qui arrest const.
abcdabcd987
5
@ abcdabcd987 Ce n'est pas correct pour plusieurs raisons. Premièrement, Proxy::operator[]ne renvoie pas de référence dans ce code (en supposant que votre commentaire ne soit pas une réponse à Ryan Haining). Plus important encore, si arrest const operator[]ne peut pas être utilisé. Vous auriez à définir une version const, et bien sûr vous la feriez revenir const Proxy. Alors Proxylui-même aurait des méthodes const et non-const. Et puis votre exemple ne se compilerait toujours pas, et le programmeur serait heureux que tout va bien dans l'univers. =)
paddy
21

Une expression x[y][z]nécessite que x[y]s'évalue à un objet dqui prend en charge d[z].

Cela signifie qu'il x[y]devrait s'agir d'un objet avec un operator[]qui s'évalue en un "objet proxy" qui prend également en charge un operator[].

C'est la seule façon de les enchaîner.

Vous pouvez également surcharger operator()pour prendre plusieurs arguments, de sorte que vous puissiez appeler myObject(x,y).

Courses de légèreté en orbite
la source
Pourquoi la surcharge des parenthèses permet d'obtenir deux entrées mais vous ne pouvez pas faire la même chose avec les parenthèses?
A. Frenzy
19

Pour un tableau à deux dimensions, en particulier, vous pourriez vous en sortir avec une seule surcharge d'opérateur [] qui renvoie un pointeur vers le premier élément de chaque ligne.

Vous pouvez ensuite utiliser l'opérateur d'indexation intégré pour accéder à chaque élément de la ligne.

Bo Persson
la source
4
Cela me semble être la solution la plus pratique et la plus efficace. Je me demande pourquoi il n'obtient pas plus de votes - peut-être parce qu'il n'a pas le code accrocheur.
Yigal Reiss
16

C'est possible si vous retournez une sorte de classe proxy lors du premier [] appel. Cependant, il existe une autre option: vous pouvez surcharger operator () qui peut accepter n'importe quel nombre d'arguments ( function(3,3)).

John
la source
9

Une approche consiste à utiliser 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];
}

Bien sûr, vous pouvez typedeflepair<int,int>

Ajay
la source
8
Cela devient beaucoup plus attrayant avec C ++ 11 et l'initialisation d'accolades. Maintenant, vous pouvez écrirenValue = theArray[{2,3}];
Martin Bonner soutient Monica le
5

Vous pouvez utiliser un objet proxy, quelque chose comme ceci:

#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;
}
Nœud
la source
4

Ça va être génial si vous pouvez me faire savoir ce que function, function[x]et function[x][y]êtes. Mais de toute façon permettez-moi de le considérer comme un objet déclaré quelque part comme

SomeClass function;

(Parce que vous avez dit que c'est une surcharge d'opérateurs, je pense que vous ne serez pas intéressé par un tableau comme SomeClass function[16][32];)

Il en functionva de même pour une instance de type SomeClass. Ensuite, recherchez la déclaration de SomeClasspour le type de retour de operator[]surcharge, tout comme

ReturnType operator[](ParamType);

Ensuite, function[x]aura le type ReturnType. Encore une fois, recherchez ReturnTypela operator[]surcharge. S'il existe une telle méthode, vous pouvez alors utiliser l'expression function[x][y].

Note, à la différence function(x, y), function[x][y]sont 2 appels distincts. Il est donc difficile pour le compilateur ou le runtime de garantir l'atomicité sauf si vous utilisez un verrou dans le contexte. Un exemple similaire est, libc dit que printfc'est atomique alors que les appels successifs au operator<<flux de sortie surchargé ne le sont pas. Une déclaration comme

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

peut avoir un problème dans une application multi-thread, mais quelque chose comme

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

c'est bien.

neuront
la source
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;
}
Ray Kaustav
la source
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];
};

J'ai trouvé ma propre solution simple à cela.

Grandstack
la source
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)};}

Cela vous permet de prendre un lambda et de produire un indexeur (avec []support).

Supposons que vous ayez un operator()qui prend en charge le passage des deux coordonnées à onxe comme deux arguments. Maintenant, le [][]support d' écriture est juste:

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

Et.. Voila. Aucune classe personnalisée requise.

Yakk - Adam Nevraumont
la source
2

Si, au lieu de dire un [x] [y], vous souhaitez dire un [{x, y}], vous pouvez faire comme ceci:

struct Coordinate {  int x, y; }

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

Il est possible de surcharger plusieurs [] en utilisant un gestionnaire de modèle spécialisé. Juste pour montrer comment cela fonctionne:

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

Et maintenant, la définition de SubscriptHandler<ClassType,ArgType,RetType,N>pour faire fonctionner le code précédent. Cela montre seulement comment cela peut être fait. Cette solution est optimale ni sans bogue (pas threadsafe par exemple).

#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
la source
0

Avec a std::vector<std::vector<type*>>, vous pouvez créer le vecteur intérieur à l'aide d'un opérateur d'entrée personnalisé qui itère sur vos données et renvoie un pointeur vers chaque donnée.

Par exemple:

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

Exemple en direct

Cette solution a l'avantage de vous fournir un véritable conteneur STL, vous pouvez donc utiliser des boucles for spéciales, des algorithmes 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;

Cependant, il crée des vecteurs de pointeurs, donc si vous utilisez de petites structures de données comme celle-ci, vous pouvez directement copier le contenu à l'intérieur du tableau.

Geoffroy
la source
0

Exemple de code:

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
la source
0

vector <vector <T>> ou T ** n'est requis que lorsque vous avez des lignes de longueur variable et bien trop inefficaces en termes d'utilisation de mémoire / d'allocations si vous avez besoin d'un tableau rectangulaire, envisagez de faire des maths à la place! voir la méthode 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
la source
0

En utilisant C ++ 11 et la bibliothèque standard, vous pouvez créer un très beau tableau à deux dimensions en une seule ligne de code:

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;

En décidant que la matrice interne représente les lignes, vous accédez à la matrice avec une myMatrix[y][x]syntaxe:

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

Et vous pouvez utiliser à distance forpour la sortie:

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

(Décider des arraycolonnes de représentation internes permettrait une foo[x][y]syntaxe, mais vous devrez utiliser des for(;;)boucles plus maladroites pour afficher la sortie.)

Jack Deeth
la source
0

Mes 5 cents.

Je savais intuitivement que je devais faire beaucoup de code passe-partout.

C'est pourquoi, au lieu de operator [], j'ai fait un opérateur surchargé (int, int). Puis en résultat final, au lieu de m [1] [2], j'ai fait m (1,2)

Je sais que c'est différent, mais c'est toujours très intuitif et ressemble à un script mathématique.

pseudo
la source
0

La solution la plus courte et la plus simple:

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

};
Végéta
la source