Home > Software engineering >  Any way to call methods with array notation instead of parentheses?
Any way to call methods with array notation instead of parentheses?

Time:10-15

In a large C project, I'm changing a struct to a class, but trying to minimise changes to any code that use the struct to make it easy to revert or reapply the change.

The struct is declared as follows:

struct tParsing {
    char* elements[23];
};

And here's the current version of the class declaration (note I've shown the elements method body in the declaration for clarity, but the real code has that separately in a CPP file):

class tParsing
{
public:
    tParsing();
    ~tParsing();

    void clear();
    char* elements(int index) {
        if (index < 0 || index > 22) return NULL;
        return fElements[index];
    };

private:
    char* fElements[23];
};

Other parts of the code have many cases like the following to get one element from the struct:
parsingInstance->elements[0]

To meet my goal of minimising changes, is there any way to make the class so that the elements method can be called using array notation (instead of parentheses) to pass the index argument, so code like the line above will work regardless of whether tParsing is a struct or a class?

CodePudding user response:

Simply introducing an operator[] in tParsing will break existing code like parsingInstance->elements[0] – but what if the member provides this operator?

class tParsing
{
    class Elements
    {
    public:
        char*& operator[](size_t index);
        char const* operator[](size_t index) const;
    };
public:
    Elements elements;
};

Now Elements class will manage the array and you retain compatibility with existing code. You might deprecate the operator so that new code is pushed towards new API (if planned, then you'd have an additional operator[] inside tParsing forwarding to Elements::operator[]).

Depending on your needs you might keep the further interface of Elements private and declare tParsing a friend of to allow all other access to the array via the latter class only.

Then some day, when you expect all instance->elements[...] calls having been eliminated, you can remove the nested class again and leave data management to tParsing directly.

CodePudding user response:

It's kind of an ugly hack, but you can keep fElements by making an inner class wrapper that overloads operator[] to access fElements, e.g. (with some test code to verify it works):

#include <iostream>


class tParsing
{
public:
    tParsing() : fElements{ new char[4]{'a', 'b', 'c', '\0'} }, elements(fElements) {}
    ~tParsing();

    void clear();

private:
    char* fElements[23];
    class elements_proxy
    {
    public:
        elements_proxy(char* elements[23]) : elements(elements) {}

        char* operator[](int index) {
            if (index < 0 || index > 22) return NULL;
            return elements[index];
        }

    private:
        char** elements;
    };


public:
    elements_proxy elements;
};

int main(int argc, char **argv) {
    auto parsingInstance = new tParsing{};

    std::cout << parsingInstance->elements[0] << std::endl;  // Outputs abc
}

Try it online!

I'm sure there are ways to simplify this (my advanced C is rusty), so I welcome any suggestions in the comments (the memory leaks are intentional, just trying to minimize the work to do something useful matching the OP's spec, without implementing actual parsing).

CodePudding user response:

Hope this helps :) See it live here.

#include <cstddef>
#include <iostream>

class tParsing {
private:
  char* fElements[23]{};
  
public:
  tParsing(): elements{this} {}  
  
  struct proxy_t {
    tParsing* p;
    char*& operator[](std::size_t index) {
      return p->fElements[index];
    }  
    char* const & operator[](std::size_t index) const {
      return p->fElements[index];
    }  
  } elements;
};
  
int main() {
  auto p = new tParsing;
  p->elements[0] = new char('x');
  std::cout << *p->elements[0] << std::endl;  
}

  •  Tags:  
  • c
  • Related